Recently I needed a SFTP client to upload file securely from local machine to remote SFTP server. I built a client myself using PHP function “ssh2_scp_send” to upload the file. It worked fine on my local testing environment, but failed on production because our system engineer guy only allowed the user to access SFTP, not SSH. After googling, it turns out that the SFTP module from phpseclib will do the job. The solution is good, and the installation worked for me without much problems. Sample code looks like the following for the upload:
// creates the connection
$sftp = new Net_SFTP($host);

// setup the key
$key = new Crypt_RSA();
$sshKeyDir = \Director\Lib\Tools\Config::getSshDir();
$key->loadKey(file_get_contents($sshKeyDir.'/id_rsa'));

// login using username and key
if(!$sftp->login($user, $key)) {
    throw new Exception("Unable to login using username: $user");
}

// create the directory if not exists
if(!$sftp->lstat($remoteDir)) {
    if(!$sftp->mkdir($remoteDir, -1, true)) {
        throw new Exception("Unable to create directory $remoteDir");
    }
}

// remove the file if already exist
if($sftp->lstat($remotePath)) {
    $sftp->delete($remotePath);
}

// upload the file
if(!$sftp->put($remotePath, file_get_contents($zipFilePath))) {
    throw new Exception("Unable to upload file $zipFilePath to $remotePath");
}
However, the installation of phpseclib breaks our PHPUnit testing: PHP Notice:  Constant CRYPT_RANDOM_IS_WINDOWS already defined   This affects all our PHPUnit test classes that have PHPUnit annotations “@runInSeparateProcess” turned on. The solution is as simply as modifying the file phpseclib/Crypt/Random.php on line 49 to:

if(!defined('CRYPT_RANDOM_IS_WINDOWS')) {
    define('CRYPT_RANDOM_IS_WINDOWS', strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');
}

However, we are using composer to manage our third party libraries and we simply can’t/don’t want to update the file. After my colleague Jeremy reading through PHPUnit’s code, we have found a solution to it without modifying the third party file. Add the following function to the PHPUnit test class that requires “@runInSeparateProcess”:
    protected function prepareTemplate($template)
    {
        if($GLOBALS['__PHPUNIT_BOOTSTRAP']) {
            $reflector = new \ReflectionClass("\PHPUnit_Util_GlobalState");
            $method = $reflector->getMethod("exportVariable");
            $method->setAccessible(true);

            $template->setVar(
                array(
                    'globals' => sprintf(
                        '$GLOBALS[\'%s\'] = %s;' . "\n",
                        "__PHPUNIT_BOOTSTRAP",
                        $method->invoke(null, $GLOBALS['__PHPUNIT_BOOTSTRAP'])
                    )
                ),
            true);
        }
    }
And then add annotation “@preserveGlobalState disabled” to all the functions/classes that are affected, for example:
    /**
     * Test file gets removed after process finishes
     * @runInSeparateProcess
     * @preserveGlobalState disabled
     */
    public function testProcessRemovesTmpFile()
    {
Now everything is back to normal. Green light in Bamboo…

Leave a Reply

Your email address will not be published. Required fields are marked *