Jeena Paradies: CacheRemoteFile Klasse

Hallo,

Ich hab gerade so ne schöne kleine Klasse geschreieben, mit Hilfe derer man Dateien, die man über das Internet holt zwischenspeichern kann um sie nicht jedes mal von einem anderen Server holen zu müssen. Ich brauche das gerade jetzt für RSS-Feeds von verschiedenen Diensten, die ich auf meiner eigenen Seite anzeigen möchte, aber nicht bei jedem aufruf der Seite diese von allen Seiten zu sammeln. Aber natürlich kann man das auch für andere Sachen nutzen.

Jetzt möchte ich von euch ein paar Meinungen zum guten code Design haben. Wie kann ich das ganze hier irgendwie sinnvoll verbessern/verkürzen/sicherer machen/erweitern, etc.? Es ist ja immer noch ziemlich abhängig von den installierten sachen. Man braucht etweder curl, PEAR:HTTP_Request oder allow_url_fopen, somit ist es nicht wirklich überall benutzbar, ich halte es aber nicht für sinnvoll alles was HTTP_Request macht nachzuprogrammieren.

<?php  
class CacheRemoteFile {  
  
  var $remote_file_url = "";  
  var $cache_file = "";  
  var $cache_lifetime = 0;  
  var $_error = array();  
  var $file = "";  
  
  function CacheRemoteFile( $remote_file_url, $cache_file = "", $cache_lifetime = 3600) {  
      $this->remote_file_url = $remote_file_url;  
      if(empty($cache_file)) $this->cache_file = md5( $this->remote_file_url ).'.cache';  
      else $this->cache_file = $cache_file;  
      $this->cache_lifetime = $cache_lifetime;  
  
      if(!$this->file = $this->read()) {  
          if(!$this->file = $this->get_file_from_url()) $this->file = "";  
          else $this->write();  
      }  
  }  
  
    function read() {  
  
        $mtime = @filemtime($this->cache_file);  
        if( !$mtime || !is_readable($this->cache_file)) {  
            $this->_error[] = "File doesn't have mtime, or isn't readable.";  
            return false;  
        }  
        elseif ( $mtime < time() - $this->cache_lifetime ) return false;  
  
        return file_get_contents($this->cache_file);  
    }  
  
    function get_file_from_url() {  
  
        if(function_exists('curl_init')) {  
            $ch = curl_init($this->remote_file_url);  
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // no output, return into variable  
            curl_setopt($ch, CURLOPT_HEADER, 0);  
            $file = curl_exec($ch);  
            curl_close($ch);  
            return $file;  
        }  
        elseif(class_exists('HTTP_Request')) {  
            $file =& new HTTP_Request($this->remote_file_url);  
            if(PEAR::isError($file->sendRequest())) {  
                $this->_error = 'The source URI does not exist.';  
                return false;  
            }  
            else return $file->getResponseBody();  
        }  
        elseif(ini_get('allow_url_fopen')) {  
            if($file = file_get_contents($this->remote_file_url)) return $file;  
            else {  
                $this->_error[] = "Feed URL not reachable.";  
                return false;  
            }  
        }  
        else {  
            $this->_error[] = "There is no 'allow_url_fopen' and no 'HTTP_Request' from PEAR and no curl support. I'm not able to get the file";  
            return false;  
        }  
    }  
  
    function write() {  
        if (!$handle = fopen($this->cache_file, "w+")) {  
            $this->_error[] = "I can't open ".$this->cache_file;  
            return false;  
        }  
        if (!fwrite($handle, $this->file)) {  
            $this->_error[] = "I can't write to ".$this->cache_file;  
            return false;  
        }  
        fclose($handle);  
        return true;  
    }  
  
  
    function get() {  
        return $this->file;  
    }  
  
    function get_errors() {  
        return $this->_errors;  
    }  
}  
?>

Grüße
Jeena Paradies

  1. Moin!

    Jetzt möchte ich von euch ein paar Meinungen zum guten code Design haben. Wie kann ich das ganze hier irgendwie sinnvoll verbessern/verkürzen/sicherer machen/erweitern, etc.? Es ist ja immer noch ziemlich abhängig von den installierten sachen. Man braucht etweder curl, PEAR:HTTP_Request oder allow_url_fopen, somit ist es nicht wirklich überall benutzbar, ich halte es aber nicht für sinnvoll alles was HTTP_Request macht nachzuprogrammieren.

    Mir sind beim Überschauen des Quellcodes [zwischen Tatort und Dittsche ;-) ] nur ein paar Kleinigkeiten aufgefallen, während diese Klasse an sich schon nicht schlecht ist, sie es als „Anregungen für spätere Versionen“:

    * Gibt es in PHP nicht die Möglichkeit, den Status einer HTTP-Abfrage zu ermitteln? Dann könnte die Klasse zusätzlich auf HTTP 304 prüfen.

    * Wieso ist das Error-Handling auf diese Art und Weise gelöst? Der Vorteil dieses Ansatzes ist „Stabilität“, weil der Programmablauf nicht durch geworfene Ausnahmen „gestört“ wird, allerdings ist es so recht einfach möglich, Fehler „zu verpassen“.

    * Wie wäre es mit einer zusätzlichen Methode:

    function has_errors() {  
        return count($this->_errors) > 1;  
    }
    

    * Es könnte praktisch sein, nach get_errors() den „Fehlercache“ zurückzusetzen.

    Viele Grüße,
    Robert

    1. Moi moin!

      * Gibt es in PHP nicht die Möglichkeit, den Status einer HTTP-Abfrage zu ermitteln? Dann könnte die Klasse zusätzlich auf HTTP 304 prüfen.

      Gute Idee:

        
      class CRF{  
       function CRF($url,$cache='',$life=36){  
        $this->cache =($cache!='') ? $cache : md5($url ).'.cache';  
        $this->content ='';  
        $this->error =array();  
        $this->host =$this->split_url($url);  
        $this->http =array( 'date'=>'',  
           'etag'=>'',  
           'content-md5'=>''  
          );  
        $f=TRUE;  
        
        if(file_exists($this->cache)){  
         clearstatcache();  
         $mtime=filemtime($this->cache);  
         if($mtime && (time()-$life)<$mtime)  
          if($this->get_cache())  
           $f=FALSE;  
        }  
        if($f)  
         if($this->get_new())  
          $this->write();  
       }  
       function split_url($url){  
        if(strpos($url,'://')!==false)  
         list($proto,$host) =explode('://',$url,2);  
        else $host=$url;  
        if(strpos($host,'/')!==false){  
         list($host,$path) =explode('/',$host,2);  
         $path='/'.$path;  
         }  
        else $path='/';  
        if(strpos($host,':')!==false)  
         list($host,$port) =explode(':',$host);  
        if(($proto=strtolower($proto))=='https' && !$port)  
         $port=443;  
        elseif(!$port)  
         $port=80;  
        
        return(array($host,$port,$path,$proto));  
       }  
       function get_cache(){  
        if(is_resource($dat=@fopen($this->cache,'r'))){  
         $this->http['date']  =trim(fgets($dat));  
         $this->http['etag']  =trim(fgets($dat));  
         $this->http['content-md5'] =trim(fgets($dat));  
         $this->content   =fread($dat,filesize($this->cache));  
         fclose($dat);  
         return(TRUE);  
        }  
        else $this->error[]='da is nischt mit Lesen im Käsch';  
        
        return(FALSE);  
       }  
       function get_new(){  
        if(file_exists($this->cache))  
         $this->get_cache();  
        
        $http=array();  
        $http[]='GET '.$this->host[2].' HTTP/1.1';  
        $http[]='Host: '.$this->host[0].($this->host[1]!=80 ? ':'.$this->host[1] : '');  
        
        if($this->http['date'])  $http[]='If-Modified-Since: '. $this->http['date'];  
        if($this->http['etag'])  $http[]='If-None-Match: '. $this->http['etag'];  
        
        $http[]="Connection: close\r\n";  
        
        if(floatval(substr(phpversion(),0,3))>=6.0){  
         if($this->host[3]=='https')  
          if(in_array('ssl',stream_get_transports()))  
           $c=@stream_socket_client('ssl://'.$this->host[0].':'.$this->host[1]);  
          else $c=FALSE;  
         else $c=@stream_socket_client('tcp://'.$this->host[0].':'.$this->host[1]);  
        }  
        else{  
         if($this->host[3]=='https')  
          $c=@fsockopen('ssl://'.$this->host[0],$this->host[1]);  
         else $c=@fsockopen($this->host[0],$this->host[1]);  
        }  
        if(!is_resource($c)){  
         $this->error[]='Keene Verbindung';  
         return(FALSE);  
        }  
        foreach($http as $v)  
         fwrite($c,$v."\r\n");  
        
        $t=substr(fgets($c),9,3);  
        if(!in_array($t,array(200,304))){  
         fclose($c);  
         $this->error[]='Serva will nich und sacht: '.$t;  
         return(FALSE);  
        }  
        elseif($t==304){  
         fclose($c);  
         touch($this->cache);  
         return(TRUE);  
        }  
        while($t!="\r\n"){  
         $t=fgets($c);  
         $x=explode(':',$t,2);  
         $x[0]=strtolower($x[0]);  
         $x[1]=(isset($x[1])) ? trim($x[1]) : '';  
        
         if($x[0]=='date'){  
          if($this->http[$x[0]]==$x[1]){  
           fclose($c);  
           touch($this->cache);  
           return(TRUE);  
          }  
          $this->http[$x[0]]=$x[1];  
         }  
         elseif($x[0]=='etag'){  
          if($this->http[$x[0]]==$x[1]){  
           fclose($c);  
           touch($this->cache);  
           return(TRUE);  
          }  
          $this->http[$x[0]]=$x[1];  
         }  
         elseif($x[0]=='content-md5'){  
          if($this->http[$x[0]]==$x[1]){  
           fclose($c);  
           touch($this->cache);  
           return(TRUE);  
          }  
          $this->http[$x[0]]=$x[1];  
         }  
        }  
        $this->content='';  
        while(!feof($c))  
         $this->content.=fgets($c);  
        
        fclose($c);  
        
        return(TRUE);  
       }  
       function write(){  
        touch($this->cache);  
        if(!is_resource($dat=fopen($this->cache,"w"))){  
         $this->error[] = "Zum Schreiben zu doof ".$this->cache;  
         return(FALSE);  
        }  
        foreach($this->http as $v)  
         fwrite($dat,$v."\n");  
        
        fwrite($dat,$this->content);  
        fclose($dat);  
        return(TRUE);  
       }  
      }  
      
      

      Gruß aus Berlin!
      eddi

      1. Berichtigung:

          
        #   if(floatval(substr(phpversion(),0,3))>=6.0){  
            if(floatval(substr(phpversion(),0,3))>=5.0){  
        
        

        Gruß aus Berlin!
        eddi

  2. echo $begrüßung;

    Jetzt möchte ich von euch ein paar Meinungen zum guten code Design haben. Wie kann ich das ganze hier irgendwie sinnvoll verbessern/verkürzen/sicherer machen/erweitern, etc.?

    Ich bin für vergrößern. Und zwar durch Kommentare. Vielleicht sogar im PHPDoc-Stil, also so wie PEAR-Klassen kommentiert sind. Außerdem finde ich einen Coding-Stil, der sich am PEAR Coding Standard zumindest orientiert angenehmer zu lesen.

    Es ist ja immer noch ziemlich abhängig von den installierten sachen. Man braucht etweder curl, PEAR:HTTP_Request oder allow_url_fopen, somit ist es nicht wirklich überall benutzbar, ich halte es aber nicht für sinnvoll alles was HTTP_Request macht nachzuprogrammieren.

    Musst du ja auch nicht. Lege einfach die verwendeten PEAR-Klassendateien deinem Projekt bei. Die sind dann wenigstens mit deiner Anwendung getestet und erzeugen keine Inkompatibilitäten, weil entweder die PEAR-Installation die benötigten Klassen nicht oder neuere oder ältere Version enthält.

    Ansonsten sah der Code beim Überfliegen unverdächtig aus.

    echo "$verabschiedung $name";

  3. Hallo,

    ... ich halte es aber nicht für sinnvoll alles was HTTP_Request macht nachzuprogrammieren.

    das ist aber das Beste, um Fehler nicht zu verschleppen.

    <?php

    class CacheRemoteFile {

    var $remote_file_url = "";
      var $cache_file = "";
      var $cache_lifetime = 0;
      var $_error = array();
      var $file = "";

    function CacheRemoteFile( $remote_file_url, $cache_file = "", $cache_lifetime = 3600) {
      }

    function read() {

    /* greifst Du mehrfach mit dieser Klasse auf ein File zu mu clearstatcache() verwendet werden */

    }

    function get_file_from_url() {

    elseif(ini_get('allow_url_fopen')) {
                if($file = file_get_contents($this->remote_file_url)) return $file;
                else {
                    $this->_error[] = "Feed URL not reachable.";
                    return false;
                }
            }
            else {

    /* Du hast noch die Möglichkeiten trotz allow_url_fopen=Off über Netzwerk funktionen zu arbeiten */

    $this->_error[] = "There is no 'allow_url_fopen' and no 'HTTP_Request' from PEAR and no curl support. I'm not able to get the file";
                return false;
            }
        }

    function write() {
        }

    function get() {

    /* auf $klasse->file kann auch so zugegriffen werden
       (Unnötige Funktion) */

    return $this->file;
        }

    function get_errors() {

    /* auf $klasse->_error kann auch so zugegriffen werden
       (Unnötige Funktion) */

    return $this->_errors;
        }
    }
    ?>

      
      
    Gruß aus Berlin!  
    eddi  
    
    
    1. Hallo,

      ... ich halte es aber nicht für sinnvoll alles was HTTP_Request macht nachzuprogrammieren.
      das ist aber das Beste, um Fehler nicht zu verschleppen.

      Ich glaube dass die PEAR Leute da schon einiges mehr an Erfahrung haben als ich ;-)

      function read() {
      /* greifst Du mehrfach mit dieser Klasse auf ein File zu mu clearstatcache() verwendet werden */

      Hm warum würde man das machen wollen? Hm ach so um den speicher frewizugeben? Die Idee ist nicht verkehrt, aber eigentlich sollte man das fast automatisieren.

      /* Du hast noch die Möglichkeiten trotz allow_url_fopen=Off über Netzwerk funktionen zu arbeiten */

      Jo aber genau das macht ja HTTP_Request auch und das nachprogrammieren finde ich irgendwie unnötig, da ich nur sicherheitslücken reinmachen wüede und nichts verbessern wüede.

      /* auf $klasse->file kann auch so zugegriffen werden
         (Unnötige Funktion) */

      Naja ich lasse grundsätzlich nicht auf klassenvariablen zugreifen, das hat mir schon einige male sehr viel Arbeit beim Erweitern gespaart.

      Grüße
      Jeena Paradies

      1. Re:

        ... ich halte es aber nicht für sinnvoll alles was HTTP_Request macht nachzuprogrammieren.
        das ist aber das Beste, um Fehler nicht zu verschleppen.
        Ich glaube dass die PEAR Leute da schon einiges mehr an Erfahrung haben als ich ;-)

        ^^ erst letztlich wurde ich dort herb enttäuscht: http://forum.de.selfhtml.org/archiv/2006/2/t124574/#m803034

        Gruß aus Berlin!
        eddi