Classe d'interface sftp

Description

Une class user friendly calibré pour les besoins de mon stage permettant principalement de récupérer et déposer de très gros fichier par SFTP ne permettant pas l'utilisation de ssh2_exec.
Les fonctions ci dessous sont beaucoup plus rapide que ssh_scp_rcv/send et surtout, non bloquante en cas d'erreur.
La classe est documentée pour doxygen.
Beaucoup de ce code à été inspiré par les "User Contributed Notes" de php.net.
Débutant, je suis ouvert à toutes vos remarques sur ce code, en particulier la gestion des erreurs ou les commentaires !
Merci d'avance pour vos contributions !

La bibliothèque ssh2 doit être installé sur le serveur pour utiliser cette classe : http://fr2.php.net/manual/fr/ssh2.setup.php.

Source / Exemple :


<?php
function debug($msg, $var = 'debug')
	{
		if (isset($_GET[$var]))
		{
			echo  trim($msg), "\n";
			ob_flush();
			flush();
		}
	}
if (!function_exists("ssh2_connect")) die("La bibliotheque ssh2 n'est pas disponible sur le serveur, pour l'installer visitez par exemple <a href=\"http://naeh.net/installer-libssh2-ssh2-pour-php/\">http://naeh.net/installer-libssh2-ssh2-pour-php</a>/\n");
/** @class sftp

  • @brief interface sftp
*
  • /
class sftp { private $id = false, $server = false, $url = false; /** @brief Constructeur *
  • Tente d'etablir une connexion SFTP à un serveur distant.
  • @param $server
  • ip/host du server
  • @param $user
  • Identifiant de la connexion
  • @param $pass
  • Mot de passe
  • @throw
  • Une exception remontera en cas d'erreur
  • /
public function __construct ($server, $user, $pass) { debug("Tentative de connexion au serveur SSH " . $server); $this->id = ssh2_connect($server, 22); if(!$this->id || empty($this->id)) throw new Exception ('Échec de connexion à ' . $server); debug("Tentative d'authentification"); if (!ssh2_auth_password($this->id, $user, $pass)) throw new Exception ("Échec d'identification à " . $server); $this->url = ssh2_sftp($this->id); if (!$this->url) throw new Exception ("Echec d'ouverture du SFTP sur le serveur " . $server); debug("SFTP ouvert"); $this->server = $server; } /** @brief Destructeur *
  • Ferme une connection sftp
  • /
public function __destruct() { debug("Fermeture de la connexion SSH\n"); ssh2_exec($this->id, "exit"); } /** @brief Retourne la taille d'un fichier sur le serveur distant
  • @param $file
  • chemin absolu du fichier
  • @return
  • taille du fichier en octet
  • /
private function getfilesize($file) { return filesize("ssh2.sftp://" . $this->url . $file); } /** @brief Recupere un fichier sur le serveur distant *
  • Tente de recuperer un fichier sur le serveur distant,
  • user friendly , $name n'a pas besoin d'être le chemin absolu vers le fichier
  • par exemple get("foo/zomg/lol.csv") va recuperer le fichier /../foo/zomg/lol.csv sur le serveur
  • dans le fichier lol.csv du dossier courant local
  • @param $name
  • nom du fichier à récuperer sur le serveur
  • @param $local_name
  • facultatif, nom du fichier en local après transfert
  • @return
  • Renvois le chemin du fichier recupéré en local
  • @throw
  • Une exception remontera en cas d'erreur
  • /
public function get($name, $local_name = false) { try {$name = $this->fpath($name);} catch (Exception $e) {throw $e;} if (!$local_name) $local_name = '.' . (strpos(name, '/') === false?strrchr($name, '/'):('/' . $name)); debug("Tentative de transfert du fichier '" .$local_name ."' depuis le serveur '" .$name . "'"); $handle = @fopen("ssh2.sftp://" . $this->url . $name, 'r'); if (!$handle) throw new Exception ("Impossible d'ouvrir le fichier " . $name . " sur le serveur distant " . $this->server); $size = $this->getfilesize($name); $contents = ''; $transfered = 0;$i = 0; while ($transfered < $size && ($buffer = fread($handle, $size - $transfered))) { debug($transfered, " transfere sur ", $size); $transfered += strlen($buffer); $contents .= $buffer; if ($i++ > 1000) { fclose($handle); throw new Exception ('Erreur pendant le transfert du fichier ' . $name . 'depuis le serveur' . $this->server . ' : timeout'); } } file_put_contents ($local_name, $contents); fclose($handle); debug("\nTransfert du fichier '" . $name . "' depuis le serveur " . $this->server . " reussi"); return ($local_name); } /** @brief Dépose un fichier sur le serveur distant *
  • Tente de déposer un fichier sur le serveur distant,
  • user friendly , $name n'a pas besoin d'être le chemin absolu vers le fichier
  • par exemple put("foo/zomg/lol.csv") va deposer le fichier lol.csv du dossier courant dans le dossier /../foo/zomg/lol.csv du serveur
  • @param $name
  • nom du fichier à déposer sur le serveur
  • @param $local_name
  • facultatif, nom du fichier
  • @throw
  • Une exception remontera en cas d'erreur
  • /
public function put($name, $remote_name = false) { try { if (!$remote_name) { $remote_name = $this->fpath($name); $name = '.' . (strpos($name, '/') === false ?('/' . $name):strrchr($name, '/')); } else $remote_name = $this->path(substr($remote_name, 0 , strrpos($remote_name, '/'))) . strrchr($remote_name, '/'); } catch (Exception $e) {throw $e;} debug("Tentative de transfert du fichier '" . $name . "' sur le serveur " . $this->server); $handle = fopen("ssh2.sftp://" . $this->url . $remote_name, 'w'); if (!$handle) throw new Exception('Impossible d\'ouvrir le fichier ' . $remote_name . ' en ecriture sur le serveur ' . $this->server); $data = file_get_contents(strpos($name, "/")?'.' . strrchr($name, "/"):$name); if ($data === false) throw new Exception("Impossible d'ouvrir le fichier local " . $name); $transfered = fwrite($handle, $data); if ($transfered === false) throw new Exception("Impossible de transferer le fichier " . $name); $size = strlen($data); if ($transfered != $size) { while ($transfered < $size && ($transfered += fwrite($handle, mb_substr($data, $transfered, $size - $transfered)))) debug($transfered . " transfere sur " . $size); } debug("Fichier " . $remote_name . " correctement depose"); fclose($handle); } /** @brief Deplace un fichier sur le serveur distant *
  • Tente de deplacer un fichier sur le serveur distant
  • user friendly , les chemins n'ont pas besoin d'être absolu
  • par exemple mv("foo/zomg/lol.csv", "omfg/lolz.txt") va deplacer /../foo/zomg/lol.csv vers /../omfg/lolz.txt
  • @param $old
  • nom du fichier à déplacer
  • @param $new
  • nouveau nom & chemin
  • @throw
  • Une exception remontera en cas d'erreur
  • /
public function mv($old, $new) { try { $old = $this->fpath($old); $new = $this->fpath($new); } catch (Exception $e) { throw $e; } debug('Tentative de deplacement du fichier \'' . $old . '\' vers \'' . $new . "'"); $old_handle = fopen("ssh2.sftp://" . $this->url .$old, 'r'); if (!$old_handle) throw new Exception ("Impossible d'ouvrir en lecture le fichier '" . $old . "' sur le serveur distant"); $new_handle = fopen("ssh2.sftp://" . $this->url .$new, 'w'); if (!$new_handle) throw new Exception ("Impossible d'ouvrir en ecriture le fichier '" . $new . "' sur le serveur distant"); $size = $this->getfilesize($old); $read = 0; $wrote = 0; while ($read < $size && ($buffer = fread($old_handle, $size - $read))) { $tmp_size = strlen($buffer); $read += $tmp_size; $tmp_wrote = 0; while ($tmp_wrote <$tmp_size) { $tmp = fwrite($new_handle, substr($buffer, $tmp_wrote)); if (!$tmp) break; $tmp_wrote += $tmp; } $wrote += $tmp_size; } debug("taille du fichier = " . $size . " , " . $read . " octets lu, " . $wrote . " octets ecrit"); fclose($new_handle); fclose($old_handle); if ($size == $read && $read == $wrote) unlink("ssh2.sftp://" . $this->url . $old); else throw new Exception ("Erreur pendant le deplacement du fichier " . $old . " en " . $old . "\ntaille du fichier = " . $size . " , " . $read . " octets lu, " . $wrote . " octets ecrit\n"); } /** @brief Récupere le chemin absolu d'un dossier *
  • @param $dir
  • nom du dossier
  • @return
  • chemin absolu du dossier $dir sur le serveur distant
  • /
private function path($dir) { $rlpath = @ssh2_sftp_realpath($this->url, $dir); if (!$rlpath) throw new Exception ("Dossier introuvable sur le serveur distant : " . $dir); return $rlpath; } /** @brief Récupere le chemin absolu d'un fichier sur le serveur *
  • @param $name
  • nom du fichier
  • @return
  • chemin absolu du fichier sur le serveur
  • /
private function fpath($name) { try { if (strpos($name, '/') === false) $name = $this->path('.') . '/' . $name; else $name = $this->path(substr($name, 0 , strrpos($name, '/'))) . strrchr($name, '/'); } catch (Exception $e) { throw $e; } return $name; } /** @brief Liste le contenu d'un dossier *
  • Liste le contenu d'un dossier passé en parametre ou le dossier courant si aucun dossié donné.
  • Ne renvois pas les dossiers
  • Offre la possibilitée de filtrer les resultats possedant l'extension "$ext" et la chaîne de caractere $like dans leur noms
  • @param $directory
  • nom du dossier
  • @param $like
  • pattern à respeceter
  • @param $ext
  • extension des fichiers à respecter
  • @return
  • renvois la liste des fichiers sous la forme d'un tableau non associatif
  • @throw
  • une exception remontera en cas d'erreur
  • /
public function flist($directory = './' , $like = false, $ext = false) { $dir = $this->path($directory); $handle = opendir("ssh2.sftp://" . $this->url . $dir); if (!$handle) throw new Exception ("Impossible d'ouvrir le dossier " . $directory . " sur le serveur " . $this->server); $files = array(); while (false !== ($file = readdir($handle))) { if (substr($file, 0, 1) != "." && !is_dir($file)) { if ($like && $ext) { if (stripos($file, $like) !== false && strrchr($file , ".") == (strpos($ext, ".") !== false?$ext:(".".$ext))) $files[] = $dir . '/' . $file; } else $files[] = $dir . '/' . $file; } } sort($files); return $files; } /** @brief Recupere une liste de fichier *
  • Recupere une liste de fichier et les deplace (sur le serveur distant) ds le dossier $dir_if_success si spécifié
  • @param $files_ar
  • tableau de fichiers
  • @param $like
  • pattern à respeceter
  • @param $ext
  • extension des fichiers à respecter
  • @return
  • renvois la liste des fichiers sous la forme d'un tableau non associatif
  • @throw
  • une exception remontera en cas d'erreur
  • /
public function get_files($files_ar, $dir_if_success = false) { $error = count($files_ar); $files = array(); foreach ($files_ar as $k =>$v) { debug("Transfert de $v."); try { $files[$k] = $this->get($v); if ($dir_if_success) $this->mv($v, $dir_if_success . strrchr($files[$k], "/")); debug("Transfert reussi"); } catch (Exception $e) { debug("Echec dans le transfer du fichier $v :\n" . $e->getMessage());; $error--; } } if (!$error) throw new Exception ("Impossible de récuperer les fichiers spécifié, vérifiez les droit, le type de transfert ou type de connexion"); if (empty($files)) return false; sort($files); return $files; } } /* Exemple d'utilisation : */ try { $_GET['debug'] = true; $sftp = new sftp(SERVER, USER, PASS); $files = $sftp->flist("data/"); print_r($files); $sftp->get_files($files, "data/treated/"); } catch (Exception $e) { die ($e->getMessage()."\n"); }

Conclusion :


A terme j'aimerais me passer des file_get_contents et file_put_contents forçant à augmenter drastiquement la mémoire alloué dans php.ini.

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.