سؤال

I'm working on a website that will act as an online SFTP client for my home machine. The solution I have so far is an index (main) php file that contains the UI of the site, and an SFTP PHP convenience class that connects with phpseclib, the SFTP connection manager.

index.php

<?php
require_once "php/Membership.php";
require_once "php/ssh.php";
require_once "php/sftp.php";

$sftp = new SFTP();

error_reporting(E_ALL);  // will report any errors your code may have
ini_set("display_errors", 1);

?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<!ATTLIST td fileName CDATA #IMPLIED>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>SFTP</title>
<link href="index.css" rel="stylesheet" type="text/css" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="index.js"></script>
</head>

<body>
<h1 id="welcome">Welcome</h1>

<div id="container">
<div id="content">

<!--SFTP Files-->
<div style="height:1000px; overflow:auto;">
<?php $sftp->set_table(NULL, NULL);?>
</div>


</div>
</div>
</body>
</html>

SFTP.php

<?php

include('Net/SFTP.php');

class SFTP {

    private $sftp;

    function __construct() {

     $this->sftp = new Net_SFTP('99.99.9999.999');
     if (!$this->sftp->login('user', 'pwd')) {
         exit('Login Failed');
     }
        echo $this->sftp->pwd() . "\r\n";
    }

    function set_table($table, $directory) {
        if (isset($directory)) {
            $this->sftp->chdir($directory);
        }
        echo '<table id="sftpTable" style="border:1px solid;">';
        $result = $this->sftp->nlist();
        foreach ($result as $row) {
            if (substr($row, 0, 1) != '.') {
                echo "<tr>" . "<td class='columnSelect' id=" . $row . "><form method='post' action=''>" . $row . "<input type=\"hidden\" name=\"index\" value=\"" . $row . "\" /></form></td>";
                if (strpos($row,'.') !== false)
                    echo '<td>'. $this->parseBytes($this->sftp->_size($row)) . '</td></tr>';
            }   
        }
        echo '</table>';
    }

    function parseBytes($bytes) {
        if ($bytes / 1074000000 >= 1) {
            return $bytes / 1074000000 . 'GB';
        }
        if ($bytes / 1048576 >= 1) {
            return $bytes / 1048576 . 'MB';
        }
        if ($bytes / 1024 >= 1) {
            return $bytes / 1024 . 'KB';
        }
        return $bytes . 'B'; 
    }
}

?>

Now the problem I'm facing is one of what appears to be circular logic. My initial thinking was to have a system that worked in the following way:

  1. Establish a singleton connection to SFTP
  2. Make queries on that singleton
  3. Present results in UI

I plan to show a table with clickable rows representing the different items in a directory on the server. When the user clicks on one of those rows, I want the system to return the new listing of items for that directory, and to update the UI accordingly. In order to accomplish this, I tried adding a hidden field to each table row to hold the name of the listing for that cell. When the cell is clicked, I would then need to extract the value of that hidden field and reset the table with the new directory. Then arises the issue of replacing the table onscreen and not just echoing out a new one.

Therefore, my question is:

What is the best way to store the directory pertaining to each cell in such a way that when that cell is clicked, the SFTP singleton resets the one table based on the new directory?

Do note that the above code likely has logical errors that may make little sense to a new viewer, as many different attempts have been made. Also, to be clear, I am looking for a methodological point in the right direction, not someone to write code for me.

Thanks in advance.

هل كانت مفيدة؟

المحلول

In PHP, the singleton paradigm is considered "bad practice". See ref: Best practice on PHP singleton classes

Although your SFTP Class does not appear to actually implement singleton logic in the example above.

Your next challenge is that when you call a webpage as you are, the entire webpage is going to be reloaded. With out some sort of server side caching, there is no way in your current architecture to only update a portion of the page. As mentioned in the comments, AJAX is going to be your tool of choice to do this.

Singletons simply do not exist BETWEEN browser calls to the web server in PHP. Every time your browser hits the web server, it will necessarily create a new SFTP connection. Every time your web server completes the request, it will destroy the SFTP connection it was using.

The Methodological Pointer you are looking for:

  1. Have a page that loads the initial UI (lets call it a view), in the browser. You probably need to provide some basic configuration at a minimum, you might simply have it load 100% on this first call.
  2. Develop some other page(s) that just serve data, preferably in json format.
  3. Have javascript (ajax) in the view that makes discrete calls back to the server for data (your tables).
  4. As you click around the page, the ajax makes apropo discrete calls to the correct page to serve up the right data, and then refreshes/updates the correct element in the UI.

I am personally a fan of Memcached for caching session data between server requests. That being said, there are a large number of caching services that can be used for holding the directory listings on the webserver until it needs to be refreshed from the SFTP server.

As you research the optimum cache solution for your challenge, it is worth while to ensure you understand the difference between opcode caching (There are many opcode caches available for consumption. You have APC, XCache, eAccelerator and Zend Platform.) and data caching (session, variable, userland - we recommend memcached).

However, if your data is large enough (>1MB) you don't typically want to cache that in anything like memcached, you would want to cache it to the local filesystem, here is an example of how I recently did this for a very large array.

/**
 * Will serialize, then write the array to disk, returning the filePath
 * 
 * @param array $array
 * @param string $filePath
 * @return string
 */
function putCacheData(array $array, $filePath = NULL){
    if (empty($filePath)){
        $filePath = tempnam(NULL, 'IMPORT');
    }
    $serializedData = serialize($array);
    file_put_contents($filePath, $serializedData);
    return $filePath;
}

/**
 * Reads the file, unserializes the data, and returns the array.
 * 
 * @param string $filePath
 * @return Array|FALSE
 */
function getCacheData($filePath){
    $array = array();
    if (empty($filePath)){
        logmessage("The filepath: [$filepath] is empty!");
        return $array;
    }

    if (! is_file($filePath)){
        putImportData($array, $filePath);
        return $array;
    }

    return unserialize( file_get_contents( $filePath ) );   
}

Then we just store the $filePath in the users session data (which goes to memcached for us) and as I bootstrap each request, I can check the session for the path, load the cached data, determine if it is expired or not, and optionally refresh it. Just ensure to write the data to file before the active request ends.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top