Du bist hier: StartPHPKlassen › Redaxo-SQL-KlasseDieses Snippet kommentieren

Redaxo-SQL-Klasse

Diese Klasse erleichtert etwas den Umgang mit Datenbanken in Redaxo. Es stehen Hilfsmethoden wie fetch(), iquery() und count() zur Verfügung. Außerdem kann das WV_SQL-Objekt als Iterator verwendet werden.

<?php

/**
 * @defgroup internal Interne API
 */

/**
 * Hilfsklasse für die SQL-Verwaltung
 * 
 * Diese Klasse stellt einige komfortable Methoden zum Umgang mit der Datenbank
 * bereit. Ihre Verwendung ist der Redaxo-Standardklasse in jedem Fall
 * vozuziehen.
 * 
 * Eine Instanz dieser Klasse kann direkt als Iterator (z.B. in einer
 * foreach-Schleife) verwendet werden.
 * 
 * @ingroup internal
 */
class WV_SQL implements Iterator {
    const ASSOC = 0;  ///< int  gibt an, dass nur ein assoziatives Array geholt werden soll
    const BOTH  = 1;  ///< int  gibt an, dass ein sowohl assoziatives als auch numerisches Array geholt werden soll
    const NUM   = 2;  ///< int  gibt an, dass nur ein numerisches Array geholt werden soll

    private $sql          = null;                ///< WV_SQL   das verwendete %WV_SQL-Objekt
    private $lastQuery    = '';                  ///< string   die zuletzt ausgefühte Abfrage
    private $numRows      = array(0);            ///< int      Anzahl der Zeilen in den einzelnen Results
    private $numCols      = array(0);            ///< int      Anzahl der Spalten in den einzelnen Results
    private $result       = array(null);         ///< array    Array aus Result-Objekten
    private $currentRow   = array(null);         ///< array    für den Iterator
    private $iteratorMode = array(self::ASSOC);  ///< array    die Iterator-Modi für jedes Result
    private $depth        = 0;                   ///< int      die aktuelle Tiefe (Anzahl an Results)

    private static $instance = null;  ///< rex_sql  das rex_sql-Objekt, das intern verwendet wird

    /**
     * Singleton
     * 
     * Diese Methode gibt die einzig erlaubte WV_SQL-Instanz zurück.
     * 
     * @return WV_SQL  die Instanz der Klasse
     */
    public static function getInstance() {
        if ( !self::$instance ) self::$instance = new WV_SQL(rex_sql::getInstance());
        return self::$instance;
    }

    /**
     * Konstruktur
     * 
     * @param rex_sql $sql  das zu verwendende rex_sql-Objekt
     */
    private function __construct(rex_sql $sql) {
        $this->sql = $sql;
    }

    /**
     * Tabellen-Präfix ermitteln
     *
     * Wrapper um $REX, um zu vermeiden, dass die Methoden des AddOns immer
     * wieder "global REX" schreiben müssen.
     * 
     * @return string  das Tabellen-Präfix von Redaxo
     */
    public static function getPrefix() {
        global $REX;
        return $REX['TABLE_PREFIX'];
    }
    
    /**
     * Datenbank auswählen
     * 
     * Ändert die ab sofort zu verwendende Datenbank.
     * 
     * @param int $databaseID  die ID der Datenbank (1 oder 2)
     */
    public function selectDB($databaseID) {
        $this->sql->selectDB($databaseID);
    }

    /**
     * Hilfsmethode für genau eine Zeile
     *
     * Diese Methode dient dazu, genau eine Zeile zu holen. Sie gibt im
     * Erfolgsfall kein true, sondern die geholte Zeile zurück, wodurch ein
     * Aufruf von row() entfällt. Sollte das Ergebnis nur eine Spalte haben, so
     * wird direkt dieser Wert zurückgeliefert (und kein Array mit einem
     * Element).
     *
     * @param  string $what              Spalten, die geholt werden sollen
     * @param  string $from              Tabelle, aus der gelesen werden soll
     * @param  string $where             WHERE-Kriterium
     * @param  int    $mode              der Modus, in dem die Zeilen dann zurückgegeben werden sollen
     * @param  bool   $includeRexPrefix  wenn true, wird $REX['TABLE_PREFIX'] vor den Tabellennamen gesetzt
     * @return mixed                     false im Falle eines Fehlers, sonst mixed oder ein Array (je nach Spaltenanzahl)
     */
    public function fetch($what, $from, $where = '1', $mode = self::ASSOC, $includeRexPrefix = true) {
        $query = sprintf('SELECT %s FROM %s%s WHERE %s LIMIT 1',
            $what,
            $includeRexPrefix ? self::getPrefix() : '',
            $from,
            $where);

        if ( ($result = $this->query($query, '', $mode, true)) === false ) {
            return false;
        }

        $row = array();

        switch ( $mode ) {
            case self::BOTH : $row = mysql_fetch_array($result); break;
            case self::NUM  : $row = mysql_fetch_row($result); break;
            case self::ASSOC:
            default         : $row = mysql_fetch_assoc($result);
        }

        mysql_free_result($result);

        if ( $row === false ) {
            return false;
        }

        if ( count($row) == 1 ) {
            $ret = array_values($row);
            return $ret[0];
        } else {
            return $row;
        }
    }

    /**
     * Abfrage für den Iterator ausführen
     *
     * Diese Methode arbeitet exakt so wie auch query(), mit dem Unterschied,
     * dass sie im Erfolgsfall nicht true, sondern eine Referenz auf das Objekt
     * zurückgibt, über das direkt iteriert werden kann. So kann man bei kurzen
     * Abfragen nochmal eine Zeile Code einsparen.
     *
     * @param  string $query             die auszuführende Abfrage
     * @param  string $replaceRexPrefix  wenn nichtleer, wird in dem Query jedes Vorkommen durch TABLE_PREFIX ersetzt
     * @param  int    $mode              der Modus, in dem die Zeilen dann zurückgegeben werden sollen
     * @return mixed                     ein leeres Array, wenn etwas schief lief, oder eine Referenz auf sich selbst im Erfolgsfall
     */
    public function iquery($query, $replaceRexPrefix = '', $mode = self::ASSOC) {
        if ( !empty($replaceRexPrefix) ) {
            $query = str_replace($replaceRexPrefix, self::getPrefix(), $query);
        }

        $this->lastQuery = $query;

        if ( gettype($this->result[$this->depth]) == 'resource' ) {
            mysql_free_result($this->result[$this->depth]);
        }

        $this->result[$this->depth]       = mysql_query($query, $this->sql->identifier);
        $this->currentRow[$this->depth]   = null;
        $this->iteratorMode[$this->depth] = $mode;

        if ( $this->result[$this->depth] === false ) {
            $this->numRows[$this->depth] = 0;
            $this->numCols[$this->depth] = 0;
            return array();
        }

        if ( is_resource($this->result[$this->depth]) ) {
            $this->numRows[$this->depth] = mysql_num_rows($this->result[$this->depth]);
            $this->numCols[$this->depth] = mysql_num_fields($this->result[$this->depth]);
        } else {
            $this->numRows[$this->depth] = 0;
            $this->numCols[$this->depth] = 0;
        }

        return $this;
    }

    /**
     * Abfrage ausführen
     *
     * Diese Methode führt genau eine Abfrage aus und liefert im Erfolgsfall
     * true, sonst false zurück. Wird der Parameter $temp auf true gesetzt, so
     * belegt die Abfrage nicht das aktuelle, interne Resultset, sondern legt
     * eine neue Variable an und gibt auch diese nach erfolgter Abfrage zurück.
     * fetch() nutzt diesen Mechanismus, um vom internen Resultset unabhängig zu
     * sein.
     *
     * @param  string $query             die auszuführende Abfrage
     * @param  string $replaceRexPrefix  wenn nichtleer, wird in dem Query jedes Vorkommen durch TABLE_PREFIX ersetzt
     * @param  int    $mode              der Modus, in dem die Zeilen dann zurückgegeben werden sollen
     * @param  bool   $temp              true, wenn die Abfrage nicht das interne Resultset belegen soll
     * @return mixed                     false bei einem Fehler, true wenn alles i.O.
     */
    public function query($query, $replaceRexPrefix = '', $mode = self::ASSOC, $temp = false) {
        if ( !empty($replaceRexPrefix) ) {
            $query = str_replace($replaceRexPrefix, self::getPrefix(), $query);
        }

        $this->lastQuery = $query;

        if ( $temp ) {
            $result = mysql_query($query, $this->sql->identifier);
            return $result ? $result : false;
        }

        if ( gettype($this->result[$this->depth]) == 'resource' ) {
            mysql_free_result($this->result[$this->depth]);
        }

        $this->result[$this->depth]       = mysql_query($query, $this->sql->identifier);
        $this->currentRow[$this->depth]   = null;
        $this->iteratorMode[$this->depth] = $mode;

        if ( $this->result[$this->depth] === false ) {
            $this->numRows[$this->depth] = 0;
            $this->numCols[$this->depth] = 0;
            return false;
        }

        if ( is_resource($this->result[$this->depth]) ) {
            $this->numRows[$this->depth] = mysql_num_rows($this->result[$this->depth]);
            $this->numCols[$this->depth] = mysql_num_fields($this->result[$this->depth]);
        } else {
            $this->numRows[$this->depth] = 0;
            $this->numCols[$this->depth] = 0;
        }

        return true;
    }

    /**
     * Liefert ein Array zurück
     *
     * Diese Methode läuft über ein Resultset und gibt ein
     * normales Array mit dem Wert zurück. Es darf dazu nur
     * ein einzelnes Feld selektiert werden. Werden zwei
     * Felder selektiert, so ist das erste der Schlüssel und
     * der zweite der Wert des Ergebnisarrays.
     *
     * @param  string $query             die auszuführende Abfrage
     * @param  string $replaceRexPrefix  wenn nichtleer, wird in dem Query jedes Vorkommen durch TABLE_PREFIX ersetzt
     * @return array                     ein Array mit den Werten
     */
    public function getArray($query, $replaceRexPrefix = '') {
        if ( !$this->query($query, $replaceRexPrefix, self::ASSOC) ) return array();
        if ( $this->cols() == 0 ) return array();

        $result = array();

        foreach ( $this as $row ) {
            if ( $this->cols() == 1 ) $result[] = reset($row);
            elseif ( $this->cols() == 2 ) $result[reset($row)] = next($row);
            else {
                $columns = array_slice(array_keys($row),1);
                foreach ( $columns as $col ) {
                    $result[reset($row)][$col] = $row[$col];
                }
            }
        }

        return $result;
    }

    /**
     * Zeilen zählen
     *
     * Diese Methode liefert die Anzahl der Zeilen in einer Tabelle, die auf ein
     * bestimmtes Kriterium passen.
     *
     * Intern wird fetch("COUNT(*) AS count", $table, $where) aufgerufen.
     *
     * @param  string $table             die zu untersuchende Tabelle
     * @param  string $where             WHERE-Kriterium
     * @param  bool   $includeRexPrefix  wenn true, wird $REX['TABLE_PREFIX'] vor den Tabellennamen gesetzt
     * @return mixed                     false im Falle eines Fehlers, sonst int
     */
    public function count($table, $where = '1', $includeRexPrefix = true) {
        $ret = $this->fetch('COUNT(*) AS count', $table, $where, $includeRexPrefix);
        return $ret ? intval($ret['count']) : false;
    }

    /**
     * Liefert eine Zeile
     *
     * Diese Methode liefert genaue eine Zeile aus dem Resultset, wird aber
     * durch die Iterator-Fähigkeit eher selten benötigt.
     *
     * @param  string $mode  der Modus, in dem die Zeilen dann zurückgegeben werden sollen
     * @return mixed         ein lerres Array im Falle eines Fehlers, sonst ein gefülltes Array
     */
    public function row($mode = self::ASSOC) {
        if ( $this->result[$this->depth] === null ) {
            return array();
           }

           switch ( $mode ) {
            case self::BOTH : $row = mysql_fetch_array($this->result[$this->depth]); break;
            case self::NUM  : $row = mysql_fetch_row($this->result[$this->depth]); break;
            case self::ASSOC:
            default         : $row = mysql_fetch_assoc($this->result[$this->depth]);
        }

        return $row;
    }

    /**
     * die letzte ID
     *
     * Gibt die zuletzt durch AUTO_INCREMENT eingefügte ID zurück.
     *
     * @return int  die letzte ID
     */
    public function lastID() {
        return mysql_insert_id($this->sql->identifier);
    }

    /**
     * die betroffenen Zeilen
     *
     * Gibt die Anzahl der von der letzten Abfrage betroffenen Zeilen zurück.
     *
     * @return int  die betroffenen Zeilen
     */
    public function affectedRows() {
        return mysql_affected_rows($this->sql->identifier);
    }

    /**
     * Neues Resultset anlegen
     *
     * Um verschachtelte Abfragen zu ermöglichen, kann mit dieser Methode ein
     * neues, leeres Resultset auf den Stack gelegt werden, das danach für alle
     * weiteren Abfragen herhalten wird.
     */
    public function in() {
        ++$this->depth;

        $this->result[$this->depth]       = null;
        $this->currentRow[$this->depth]   = null;
        $this->iteratorMode[$this->depth] = self::ASSOC;
        $this->numRows[$this->depth]      = 0;
        $this->numCols[$this->depth]      = 0;
    }

    /**
     * Vorheriges Resultset nutzen
     *
     * Entfernt das oberste Resultset und setzt den Zeiger auf das vorherige.
     * Entspricht einer POP-Operation bei Stacks. Jeder in()-Aufruf sollte
     * irgendwann von einem out()-Aufruf gefolgt werden.
     */
    public function out() {
        if ( is_resource($this->result[$this->depth]) ) {
            mysql_free_result($this->result[$this->depth]);
        }

        --$this->depth;

        if ( $this->depth < 0 ) {
            $this->depth = 0;
        }
    }

    /**
     * die letzte Abfrage
     *
     * Gibt die zuletzt ausgeführte Afrage zurück
     *
     * @return string  die letzte Abfrage
     */
    public function getLastQuery() {
        return $this->lastQuery;
    }

    /**
     * Anzahl der Zeilen
     *
     * Diese Methode gibt die Anzahl der mit der letzten Abfrage geholten Zeilen
     * zurück. Dieser Wert ist nicht immer definiert (z.B. nicht für
     * UPDATE-Queries).
     *
     * @return int  die Anzahl der Zeilen
     */
    public function rows() {
        return $this->numRows[$this->depth];
    }

    /**
     * Anzahl der Spalten
     *
     * Diese Methode gibt die Anzahl der mit der letzten Abfrage geholten
     * Spalten zurück. Dieser Wert ist nicht immer definiert (z.B. nicht für
     * UPDATE-Queries).
     *
     * @return int  die Anzahl der Spalten
     */
    public function cols() {
        return $this->numCols[$this->depth];
    }

    /**
     * Gibt den letzten Fehlercode zurück.
     *
     * @return int  den letzten Fehlercode
     */
    public function getErrno() {
        return mysql_errno($this->sql->identifier);
    }

    /**
     * Gibt die letzte Fehlermeldung zurück.
     *
     * @return string  die letzte Fehlermeldung
     */
    public function getError() {
        return mysql_error($this->sql->identifier);
    }

    // =========================================================================
    // ITERATOR-METHODEN
    // =========================================================================
    
    ///@cond INCLUDE_ITERATOR_METHODS

    public function current() {
        return $this->currentRow[$this->depth];
    }
    
    public function next() {
        $this->currentRow[$this->depth] = $this->row($this->iteratorMode[$this->depth]);
    }
    
    public function key() {
        return null;
    }
    
    public function valid() {
        if ( $this->result[$this->depth] === false ) {
            return false;
        }

        // Wurde noch gar keine Zeile geholt? Dann holen wir das hier nach.
        if ( $this->currentRow[$this->depth] === null ) {
            $this->currentRow[$this->depth] = $this->row($this->iteratorMode[$this->depth]);
        }

        return is_array($this->currentRow[$this->depth]);
    }
    
    public function rewind() {
        if ( mysql_num_rows($this->result[$this->depth]) > 0 ) {
            mysql_data_seek($this->result[$this->depth], 0);
            $this->currentRow[$this->depth] = null;
        }
    }
    
    ///@endcond
}

?>

Kommentar verfassen

Fehler gefunden? Doofer Code? Ein kleines "Danke!"? Hinterlasse einfach einen Kommentar.

(muss sein)
(muss nicht sein, wird nicht angezeigt)

Dein Kommentar wird erst nach einer manuellen Prüfung angezeigt.