We launched new forums in March 2019—join us there. In a hurry for help with your website? Get Help Now!
    • 24865
    • 289 Posts
    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. smiley

    Thanks in advance lads!
      @MarkGHErnst

      Developer at Adwise Internetmarketing, the Netherlands.
      • 22303 MODX Staff
      • 10,725 Posts
      It should be functional with two instances using different drivers, but it does appear it could be a bug.

      That said, did you implement the entire 'salesforce' driver implementation in xPDO? I'm curious how this is working at all for salesforce; you are saying it is compatible with the mysql PDO driver?
        • 24865
        • 289 Posts
        Yes, it occurs to me as well that this might be a bug. I filed it as a feature but I think this classifies it as a bug.

        http://tracker.modx.com/issues/9062

        To answer your question, no, SalesForce is not compatible with PDO, since it implements the same tech on the backend. It's purely meant to support xPDO's caching, query building, object retrieval, manipulation and validation. The porting of releated objects is completely ignored.

        I'm also injecting a custom connection. Where you would say $xpdo->connect(), I'm doing a custom connection (via the Soap protocol, the SFDC way) and injecting that one into xPDO under $xpdo->pdo and $xpdo->connection->pdo.

        Since xPDO calls on $this->pro for every core function, such as last insert id, preparing/executing of the query and the query itself, this would all go via the SFDC wrapper (which basically replaces the PDO wrapper).

        Is it a bug which I can solve right now or do I have to wait for some kind of updated version? The only way forward with this "bug" in place is to extend xPDO like MODX and create a custom wrapper while still relying on your main class.
          @MarkGHErnst

          Developer at Adwise Internetmarketing, the Netherlands.
          • 34012
          • 88 Posts
          I've had this idea in between my ears for couple months also, the salesforce driver.

          In my opinion you cannot use xPDO directly as it uses PDO all the way down till the end. You can write an xPDO like driver for SF. And use the xPDOQuery class as base, you also need to modify the xPDOObject class quite heavily for it as that is where the query is actually run. Also many of the xPDO functions rely on PDO functions and does not really simulate the situations except with my few non supported drivers wink

          That said, check xPDOObject also, there is plenty of places you need to modify. Can check from my https://github.com/lazylegs account how I've come over through the stuff with Oracle and PostgreSQL where Oracle was more pain in the bum.

          As with experience from SF I see what you are trying to do, but you are heavily trying to bend the thing and manipulate the PDO out. With derived class over xPDO it could be doable, but I would say it is easier to take advantage of the caching by taking code from the xPDO and adding it to SF as the SF API was pretty nifty already from what I remember working with it.
            Almost retired from web-development industry but still randomly writing at Lazylegs.info and on schedule hopefully in near future to finish Oracle and PostgreSQL ports of MODX
            • 28042 ☆ A M B ☆
            • 24,524 Posts
            You might want to check out the new Salseforce "Canvas"; it's not quite finished yes, but from what I saw in a webinar last week will be very interesting. http://developer.force.com/releases/release/Winter13/Forcedotcom+Canvas+Pilot

            I also found http://wiki.developerforce.com/page/Web_Services_API#PHP

            Notifications are also interesting; they are automatic SOAP functions that can be triggered on various Salesforce actions; it's not difficult to create a SOAP server for Revo to make use of this feature. https://github.com/freejung/wxSalesforce
              Studying MODX in the desert - http://sottwell.com
              Tips and Tricks from the MODX Forums and Slack Channels - http://modxcookbook.com
              Join the Slack Community - http://modx.org
              • 24865
              • 289 Posts
              Thanks for the replies Susan and LazyLegs.

              I can totally agree with what you're saying there Lazy. It would require me to modify a large chunk of the original code down to getting it SalesForce compatible. Basically a waste of time, since I was only planning on using it for relation management (without the upsert/removal), caching and query building. Branching out xPDOQuery sounds like a good idea though.

              Susan, those are some pretty interesting links. My time with SF has been brief (only like 30 hours total, off and on) so I wasn't aware of some of your links. The Force PHPToolkit 20.0 is the base of my connection model I was trying to inject into xPDO as $xpdo->pdo, but thanks anyway!

              Concerning SOAP, I'm not that familiar with SOAP as a whole, but I'm getting there. smiley Looking forward to your integration.
              I just noticed this topic where you replied as well (https://forums.modx.com/thread/49002/salesforce-integration-with-modx). Sounds promising.
                @MarkGHErnst

                Developer at Adwise Internetmarketing, the Netherlands.
                • 34012
                • 88 Posts
                We got pretty much as much experience with sf then.

                Am with phone online so gonna keep this short for now. for caching you can use xPDO natively. what you could is implement the cache functions to sf object. Or just use the default method. Basically you could serialize the objects. Just an idea.
                  Almost retired from web-development industry but still randomly writing at Lazylegs.info and on schedule hopefully in near future to finish Oracle and PostgreSQL ports of MODX
                  • 24865
                  • 289 Posts
                  Yea, already got a tiny wrapper for that, but it leaves something to be desired for when it comes down to the complex queries with wild where/and/or statements. Doesn't match xPDO's level really, so that's why I'm looking to extend it.

                  I've got a couple of idea's, lemme get back to you when I got a working PoC.
                    @MarkGHErnst

                    Developer at Adwise Internetmarketing, the Netherlands.
                    • 34012
                    • 88 Posts
                    I could gladly help with the query language, that was actually one of my plans to create modForce plugin that incorporate SF completely with APEX to MODX. But that was an idea wink

                    Already thinking in big scale tongue
                      Almost retired from web-development industry but still randomly writing at Lazylegs.info and on schedule hopefully in near future to finish Oracle and PostgreSQL ports of MODX
                      • 24865
                      • 289 Posts
                      Sounds good, you should definitely add me to Skype and see if we can pick up this project ASAP. I'm still implementing the project in a site so any additional experience or lightening the load of work by creating a generic class would be great.

                      I've answered your PM with an e-mail. smiley
                        @MarkGHErnst

                        Developer at Adwise Internetmarketing, the Netherlands.