2013-01-14 62 views
6

好的,所以通過ftp或sftp訪問其他服務器...我已經寫了一個小類來處理它..它顯然是新的,可以很容易地改進,所以我以爲我會把它扔出來看看其他人的想法(stackoverflow得到了很多的意見,所以希望這可以幫助別人),以及他們如何可以改善它...所以我想問題是...怎麼可以這有待改進?PHP FTP/SFTP交換機類

class ftp_sftp{ 
//determine, if ssh, to use phpseclib or php's inbuilt ssh_sftp 'libssh' 
public $ssh_type = 'phpseclib'; 
//set ths path to the directory containing the entire phpseclib files 
public $phpseclib_path = 'scripts/phpseclib0.3.0'; 

//private vars generated by this class 
public $host; 
public $username; 
public $password; 
public $connection_type; 
public $port_number; 
public $connection = false; 

//contruct method which will attempt to set the connection details and automatically attempt to establisha connection to the server 
public function __construct($host, $username, $password, $connection_type, $port_number = false){ 

    //add the webroot to the beginning of the $this->phpseclib_path (this is bespoke to my own configuration) 
    $this->phpseclib_path = WEBROOT_PRIVATE.$this->phpseclib_path; 

    //setting the classes vars 
    $this->host   = $host; 
    $this->username  = $username; 
    $this->password  = $password; 
    $this->connection_type = $connection_type; 

    //set the port number to defaults based on connection type if none passed 
    if($port_number === false){ 
     if($connection_type == 'ftp'){ 
      $port_number = 21; 
     } else { 
      $port_number = 22; 
     } 
    } 
    $this->port_number = $port_number; 

    //now set the server connection into this classes connection var 
    $this->connection = $this->connect(); 
} 

//tests the details passed and tries to establish a connection, returns false on fail. 
function connect(){ 
    br($this->connection_type); 
    switch($this->connection_type) 
     { 
      case 'ftp': 
         $connection = ftp_connect($this->host); 
         $login = ftp_login($connection, $this->username, $this->password); 

         //if no connection was possible return false and leave $this-connection as false 
         if(!$connection || !$login){ 
          return false; 
         } else { 
          // enabling passive mode 
          ftp_pasv($connection, true); 
          return $connection; 
         } 
      break; 

      case 'sftp': 
         //decide which ssh type to use 
         switch($this->ssh_type){ 
          case 'phpseclib': 
            //inlcude the phpseclib path in the include array and include the ssh2 class 
            set_include_path($this->phpseclib_path); 
            if(!include('Net/SSH2.php')){ 
             echo 'Sorry failed to load SSH2 class'; 
             br(); 
            } 
            if(!include('Net/SFTP.php')){ 
             echo 'Sorry failed to load SFTP class'; 
             br(); 
            } 

            $connection = new Net_SFTP($this->host, $this->port_number); 
            $login = $connection->login($this->username, $this->password); 
          break; 

          case 'libssh2': 
            $connection = ssh2_connect($this->host, $this->port_number); 
            $login = ssh2_auth_password($connection, 'username', 'secret'); 
          break; 

          default: 
            echo 'No ssh method defined, please define one in: $ftp_sftp->ssh_type'; 
            exit(); 
          break; 
         } 


         //if no connection was possible return false and leave $this-connection as false 
         if (!$connection || !$login) { 
          return false; 
         } else { 
          return $connection; 
         } 
      break; 

      default: echo 'No connection type set cannot choose a method to connect'; 
      break; 
     } 
} 

//acces the phpseclib errors 
public function errors(){ 
if($this->connection_type == 'sftp' && $this->ssh_type == 'phpseclib'){ 
     print_r($this->connection->getErrors()); 
    } else { 
     echo 'no error logs available'; 
    } 
} 

//function used by this class to check certain values are set 
public function connection_check(){ 
    if($this->connection === false){ 
     echo 'Sorry there seems to be a connection problem please try again'; 
     br(); 
    } 

    if($this->connection_type === false){ 
     echo 'Sorry there seems to be a no connection type set'; 
    } 

    if($this->connection === false || $this->connection_type === false){ 
     exit(); 
    } 
} 

//transfers a file to the connected server 
public function put($targetLocationToSendTo, $existingLocationToSendFrom){ 

    //check the connection 
    $this->connection_check(); 

    switch($this->connection_type) 
     { 
      case 'ftp': 
         //ftp_put the file across 
         $put = ftp_put($this->connection, $targetLocationToSendTo, $existingLocationToSendFrom, FTP_BINARY); 
      break; 

      case 'sftp': 
         //decide which ssh type to use 
         switch($this->ssh_type){ 
          case 'phpseclib': 
            $put = $this->connection->put($targetLocationToSendTo, $existingLocationToSendFrom, NET_SFTP_LOCAL_FILE); 
          break; 

          case 'libssh2': 
            $put = ssh2_scp_send($this->connection, $targetLocationToSendTo, $existingLocationToSendFrom, 0755); 
          break; 
         } 
      break; 
     } 

    return $put; 
} 

//list the contents of a remote directory 
public function dir_list($dirToList){ 

    //check the connection 
    $this->connection_check(); 

    //run appropriate list 
    switch($this->connection_type) 
     { 
      case 'ftp': 
         $list = $this->connection = ftp_nlist($this->connection, $dirToList); 
      break; 

      case 'sftp': 
         //decide which ssh type to use 
         switch($this->ssh_type){ 
          case 'phpseclib': 
            $list = $this->connection->nlist($dirToList); 
          break; 

          case 'libssh2': 
            echo 'Sorry there is no support for nlist with libssh2, however this link has a possible answer: http://randomdrake.com/2012/02/08/listing-and-downloading-files-over-sftp-with-php-and-ssh2/'; 
          break; 
         } 
      break; 
     } 

    return $list; 
} 

//get the timestamp of the file on another server 
public function remote_filemtime($pathToFile){ 

    //check the connection 
    $this->connection_check(); 

    //run appropriate list 
    switch($this->connection_type) 
     { 
      case 'ftp': 
         $timeStamp = ftp_mdtm($this->connection, $pathToFile); 
      break; 

      case 'sftp': 
         //decide which ssh type to use 
         switch($this->ssh_type){ 
          case 'phpseclib': 
            $statinfo = $this->connection->stat($pathToFile); 
          break; 

          case 'libssh2': 
            $statinfo = ssh2_sftp_stat($this->connection, $pathToFile); 
          break; 
         } 

         if($statinfo['mtime']){ 
          $timeStamp = $statinfo['mtime']; 
         } else { 
          $timeStamp = false; 
         } 
      break; 
     } 

    return $timeStamp; 
} 

//make a directory on the remote server 
public function make_dir($dirToMake){ 
    //check the connection 
    $this->connection_check(); 

    //run appropriate list 
    switch($this->connection_type) 
     { 
      case 'ftp': 
         $dir_made = ftp_mkdir($this->connection, $dirToMake); 
      break; 

      case 'sftp': 
         //decide which ssh type to use 
         switch($this->ssh_type){ 
          case 'phpseclib': 
            $statinfo = $this->connection->mkdir($dirToMake); 
          break; 

          case 'libssh2': 
            $statinfo = ssh2_sftp_mkdir($this->connection, $dirToMake, 0755); 
          break; 
         } 
      break; 
     } 

    return $dir_made; 
} 

//change directory 
public function change_dir($dirToMoveTo){ 
    //check the connection 
    $this->connection_check(); 

    //run appropriate list 
    switch($this->connection_type) 
     { 
      case 'ftp': $chdir = ftp_chdir($this->connection, $dirToMoveTo); 
      break; 

      case 'sftp': 
         //decide which ssh type to use 
         switch($this->ssh_type){ 
          case 'phpseclib': 
            $chdir = $this->connection->chdir($dirToMoveTo); 
          break; 

          case 'libssh2': 
            echo 'Sorry this feature does exist yet for when using libssh2 with the ftp_sftp class'; 
            exit(); 
          break; 
         } 
      break; 
     } 

    return $chdir; 
} 

//curent directory we are looking in 
public function pwd(){ 

    //check the connection 
    $this->connection_check(); 

    //run appropriate list 
    switch($this->connection_type) 
     { 
      case 'ftp': $pwd = ftp_pwd($this->connection); 
      break; 

      case 'sftp': 
         //decide which ssh type to use 
         switch($this->ssh_type){ 
          case 'phpseclib': 
            $pwd = $this->connection->pwd(); 
          break; 

          case 'libssh2': 
            echo 'Sorry this feature does exist yet for when using libssh2'; 
            exit(); 
          break; 
         } 
      break; 
     } 

    return $pwd; 
} 

//delete file 
public function delete_file($fileToDelete){ 
    //check the connection 
    $this->connection_check(); 

    //run appropriate list 
    switch($this->connection_type) 
     { 
      case 'ftp': $unlink = ftp_delete($this->connection, $fileToDelete); 
      break; 

      case 'sftp': 
         //decide which ssh type to use 
         switch($this->ssh_type){ 
          case 'phpseclib': 
            $unlink = $this->connection->delete($fileToDelete); 
          break; 

          case 'libssh2': 
            $unlink = ssh2_sftp_unlink($this->connection, $fileToDelete); 
          break; 
         } 
      break; 
     } 

    return $unlink; 
} }//end of class 

使用類:

$ftp_sftp = new ftp_sftp('92.21.627.163', 'ftpuser', 'yourpassword', '', 'ftp', '21'); 
echo $ftp_sftp->pwd(); 

我有一點點麻煩的phpseclib使用的EasyPHP我的win7機器上連接並已開始問答..如果任何人有任何想法,我會非常感謝... Cannot get phpseclib to connect - error 10060

+1

我建議繼承來減少這些switch語句。 – quickshiftin

+0

你能詳細說一下嗎? – John

+1

我全力以赴地解答了這個問題,因爲這是一個非常普遍的問題。希望它是有道理的。如果您掌握Strategy Pattern(最簡單的設計模式之一),我向您保證,您將立即繞過75%的開發人員技能!如果你迷上了模式,拿起標準包,你就會成爲忍者。 – quickshiftin

回答

13

主要問題與此代碼是它鱗垂直。假設您決定添加另一個實現,一個本地文件系統,爲'文件'$connection_type引入第三個潛在值。現在你必須經過代碼加入case 'file':的條件無處不在,使得ftp_sftp失控。你現在在'sftp'代碼分支中再次遇到同樣的問題,處理$ssh_type

原始代碼

switch($connection_type) { 
    case 'ftp' : // ... 
    case 'stfp' : // ... 
} 

變爲

switch($connection_type) { 
    case 'ftp' : // ... 
    case 'stfp' : // ... 
    case 'file' : // ... 
} 

請記住這是在上$connection_type切換代碼每個switch語句。想象一下,我們會添加到ftp_sftp類納入每增加一個行爲的代碼有多少行...

事實上,它看起來像你實際上有3個戰略,現在,FTPSFTP & SSH

你想在這裏做的是設置代碼,使其擴大水平而不是垂直要做到這一點是通過Strategy design pattern的方式。

基本上你在做什麼就是拉出代碼中的通用接口,然後爲FTP創建單獨的實現& SFTP。有很多不同的方式可以實現這個實際的實現,所以調整你的心臟的內容!

常見的接口

這是一個什麼樣的任何「ftp類」必須是能夠做到的定義。

interface PhpFtp 
{ 
    public function __construct($host, $username, $password, $port_number = false); 
    public function connect(); 
    public function errors(); 
    public function connection_check(); 
    public function put($targetLocationToSendTo, $existingLocationToSendFrom); 
    public function dir_list($dirToList); 
    public function remote_filemtime($pathToFile); 
    public function make_dir($dirToMake); 
    public function change_dir($dirToMoveTo); 
    public function pwd(); 
    public function delete_file($fileToDelete); 
} 

出廠

現在有貫穿檢查$connection_type碼單switch語句。您可能會決定以不同方式處理default大小寫。

class PhpFtpFactory 
{ 
    public static function create($connection_type) 
    { 
     switch($connection_type) { 
      case 'ftp': 
       $oFtp = new Ftp(); 
       break; 
      case 'sftp': 
       $oFtp = new Sftp(); 
       break; 
      default: 
       throw new UnexpectedValueExcpetion(
        'No connection type set cannot choose a method to connect'); 
     } 

     // Potential follow-up construction steps 
     return $oFtp; 
    } 
} 

基類

並非絕對必要,但非常有幫助。另外,我們偷偷在另一種設計模式中調用Template MethodBaseFtp::pwd是一種模板方法,它控制整體算法,但委託給具體的孩子一部分。

abstract class BaseFtp implements PhpFtp 
{ 
    public function pwd() 
    { 
     $this->connection_check(); 
     return $this->_pwd(); 
    } 

    abstract protected function _pwd(); 
} 

混凝土FTP實現

現在我們有一個簡潔的FTP類,只做FTP操作,因此它不知道SFTP。

class Ftp extends BaseFtp 
{ 
    protected function _pwd() 
    { 
     return ftp_pwd($this->connection); 
    } 

    public function connect() 
    { 
     $connection = ftp_connect($this->host); 
     $login  = ftp_login($connection, $this->username, $this->password); 

     // if no connection was possible return false and leave $this-connection as false 
     if(!$connection || !$login) 
      return false; 

     // enabling passive mode 
     ftp_pasv($connection, true); 
     return $connection; 
    } 
} 

混凝土SFTP類

現在我們有一個獨立的SFTP類,但是發現它接通$ssh_type就如同當初ftp_sftp類沒有爲$connection_type。一旦你掌握了策略模式,並且完成了類,你就可以在Sftp實現中重新實施戰略模式,這樣你就可以擁有2個類Phpseclib & Libssh2

class Sftp extends BaseFtp 
{ 
    protected function _pwd() 
    { 
     // decide which ssh type to use 
     switch($this->ssh_type) { 
      case 'phpseclib': 
       return $this->connection->pwd(); 

      case 'libssh2': 
       echo 'Sorry this feature does exist yet for when using libssh2'; 
       return false; 
     } 
    } 

    public function connect() 
    { 
     // decide which ssh type to use 
     switch($this->ssh_type) { 
      case 'phpseclib': 
       // inlcude the phpseclib path in the include array 
       // and include the ssh2 class 
       set_include_path($this->phpseclib_path); 
       if(!include('Net/SSH2.php')){ 
        echo 'Sorry failed to load SSH2 class'; 
        br(); 
       } 
       if(!include('Net/SFTP.php')){ 
        echo 'Sorry failed to load SFTP class'; 
        br(); 
       } 

       $connection = new Net_SFTP($this->host, $this->port_number); 
       $login = $connection->login($this->username, $this->password); 
       break; 

      case 'libssh2': 
       $connection = ssh2_connect($this->host, $this->port_number); 
       $login = ssh2_auth_password($connection, 'username', 'secret'); 
       break; 

      default: 
       echo 'No ssh method defined, please define one in: $ftp_sftp->ssh_type'; 
       exit(); 
       break; 
     } 

     if(!$connection || !$login) 
      return false; 
    } 
} 

啓用新的代碼來代替使用

隨着新系統的10X更容易地看到什麼是什麼,每個班側重於它的特定域和碼長水平。那是什麼意思?請記住上面,當我們考慮在原始模式中添加第三個$connection_type'文件'?在新的安排,我們更新一個 switch語句中,一個在工廠:

class PhpFtpFactory 
{ 
    public static function create($connection_type) 
    { 
     switch($connection_type) { 
      case 'ftp': 
       $oFtp = new Ftp(); 
       break; 
      case 'sftp': 
       $oFtp = new Sftp(); 
       break; 
      case 'file': 
       $oFtp = new File(); 
       break; 
      default: 
       throw new UnexpectedValueExcpetion(
        'No connection type set cannot choose a method to connect'); 
     } 

     // Potential follow-up construction steps 
     return $oFtp; 
    } 
} 

然後當然你添加新的具體實施

class File extends PhpFtp 
{ 
    // ... 
} 

由於新File類可以進去它是自己的空間,我們不擴大一箇中心階層,即原來的ftp_sftp階層。

爲了使用新系統,你打了工廠的實例,並從那裏

// get an instance of the Ftp class 
$ftp = PhpFtpFactory::create('ftp'); 

// get an instance of the Sftp class 
$sftp = PhpFtpFactory::create('sftp'); 

去這兩種情況下,都支持所有PhpFtp功能,因爲他們實現的接口!這也使您能夠編寫多態的代碼。考慮一個功能,類型提示對接口

// This is polymorphic code 
function doFtpStuff(PhpFtp $oFtp) { 
    // As mentioned above $oFtp can be an instance of any class that implements PhpFtp 
    $oFtp->connect(); 
    $oFtp->pwd(); 
} 
+0

哇。非常感謝你在那裏的幫助。我不能在週五之前試圖實現這一點,但會讓你知道我的相處方式 – John

0

Wordpress的SFTP實現以奇怪的方式執行chdir和pwd。他們做ssh2_exec()。不知道這是否適用於文件下載/上傳。這裏的履行情況:

http://core.svn.wordpress.org/trunk/wp-admin/includes/class-wp-filesystem-ssh2.php

我覺得phpseclib是更好,但僅供參考。

此外,最新版本的phpseclib是0.3.1。 idk ...只是另一個大聲笑。

最後,也許做include_once()而不是include()?那樣這個類可以被多次實例化?