Beiträge von cottton

    Ziel:
    eine einfache Datenbankklasse erstellen, über die so einfach wie möglich Anfragen (Queries) an den Datenbankserver geschickt werden können.
    Dabei sollen Platzhalter verwendet werden können, um SQL-Injections vermeiden zu können.

    Verwendete Techniken:
    PHP, PDO (siehe: http://php.net/manual/de/book.pdo.php)

    Schwierigkeitsgrad:
    für Anfänger geeignet, Grundkenntnisse über die Funktionsweisen von PHP, OOP und SQL vorausgesetzt

    Fehlerbehandlung:
    die Klasse wirft Exceptions, welche mit einem try/catch -Block abgefangen werden sollten.

    Was soll die Klasse können?
    Einfaches Verbinden zum Datenbankserver und Ausführen der Sql-Abfragen.

    - Erweiterung "Persistente Verbindung" und Erweiterung "Initial Commands"
    (wird evtl erweitert)

    Struktur
    Da wir die DB-Klasse ständig verfügbar haben wollen,
    ohne jedes Mal eine Instanz z.B. als Funktionsargument übergeben zu müssen,
    sind die Attribute (Variablen) und Methoden (Funktionen) unserer Klasse statisch (Schlüsselwort static).

    Die Klasse und ihre Methoden (zur Übersicht erstmal ohne Inhalt):

    Die methode ::connect()


    Die methode ::close()

    PHP
    public static function close()
        {
            self::$dbh = null;
            // wir setzen $dbh auf den wert NULL,
            // wodurch der __destruct() der in PHP vordefinierten klasse PDO ausgeführt wird
        }


    Die methode ::exe()


    Die methode ::lastInsertId()

    PHP
    public static function lastInsertId()
        {
            // hier gibts nicht viel zu tun.
            // die in PHP vordefinierte klasse PDO stellt uns diese methode zur verfügung:
            return self::$dbh->lastInsertId();
        }

    Das wars. Hier die ganze Klasse nocheinmal ohne Kommentare:

    Beispiele:

    einen Fehler beim Verbindungsaufbau provozieren und fangen (catch-block)

    PHP
    try{
        Sql::connect('localhost', 'root', 'einFalschesPasswort', 'datenbank_name');
    }catch(Exception $e){
        echo $e->getMessage();
    }


    Ausgabe:

    Code
    SQLSTATE[HY000] [1045] Access denied for user 'root'@'localhost' (using password: YES)


    einen Fehler bei Ausführen einer Query provozieren und fangen (catch-block)
    (Vorraussetzung natürlich eine gültige Verbindung)

    PHP
    try{
        Sql::connect('localhost', 'benutzername', 'password');
        $sql = "SELECT irgendwas falsches;";
        $rows = Sql::exe($sql);
        var_dump($rows);
    }catch(Exception $e){
        echo $e->getMessage();
    }


    Ausgabe:

    Code
    (SQLSTATE: 42S22) eMessage: Unknown column 'irgendwas' in 'field list' (eCode: 1054)query: SELECT irgendwas falsches; para:


    Bsp für eine Query ohne Parameter:

    PHP
    try{
        Sql::connect('localhost', 'benutzername', 'password');
        $sql = "SELECT `table_schema` AS `Datenbankname` FROM `information_schema`.`tables` GROUP BY `table_schema`;";
        $rows = Sql::exe($sql);
        echo "<pre>";
        var_dump($rows);
    }catch(Exception $e){
        echo $e->getMessage();
    }


    Ausgabe (Bsp):


    Bsp für eine Query mit Parameter:


    Ausgabe (Bsp):


    Und zuletzt nich ein unkommentiertes Beispiel:

    Wat machste denn :D
    Guck Dir mal in Ruhe den Code an. Wenn "unexpected" error auftucht, ist meistens davor irgendwas vergessen worden oder zu viel =)

    BTW: welchen Editor nutzt Du? Der sollte Dir Solche Sachen eigl schon farblich anzeigen.

    Mach langsam =)
    Wichtig ist ja vor allem, dass Du auch verstehst was Du machst/änderst.

    Ich schreibe mal in den Code mit Kommentaren:

    PHP
    // falsch
    echo '
    <td>
        <a href="anlagen_details.php?navi=2&ida=' . htmlspecialchars($row["AnlagenNr"]) .
            '&sda='%FEHLER' .  htmlspecialchars($_POST['suchstring'] . '%FEHLER') .'">
        Ändern
    </td> 
    ';
    PHP
    // richtig
    echo '
    <td>
        <a href="anlagen_details.php?navi=2&ida=' . htmlspecialchars($row["AnlagenNr"]) .
            '&sda=' . htmlspecialchars($_POST['suchstring']) . '">
        Ändern
    </td> 
    ';


    Was Du übrigens vorhast mit den % im HTML
    kann ein Endlos-Anhängen werden.

    Code
    &sda=%' .  htmlspecialchars($_POST['suchstring']) . '%


    Beim ersten Absenden des Formulars steckt (suchstring zB "Hallo")
    "%Hallo%"
    in $_POST['suchstring'].

    Beim Ausgeben hänst Du erneut 2 mal % an. Ergibt dann:
    "%%Hallo%%"
    und beim absenden .... usw.
    Also weg mit den %.

    Zwecks sql:
    Beispiel #2 : http://php.net/manual/de/mysq…-statements.php

    PHP
    $mysqli = new mysqli("example.com", "user", "password", "database");
    // ... error checks
    $stmt = $mysqli->prepare("INSERT INTO test(id) VALUES (?)");
    // ... error checks
    $stmt->bind_param("i", $id);
    // ... error checks
    $stmt->execute();
    // ... error checks
    // und jetzt kannst du die rows in der while "holen"
    while(...)

    BIND PARAM ! :D

    PHP
    $suchstr = '%' . $_POST['suchstring'] . '%'; 
    ...->bind_param("s", $suchstr)


    Du musst doch die Parameter für die ? übergeben.

    EDIT ich seh grade, da ist ja nicht mal ein $stmt->execute()

    PHP
    $suchstr = '%' . $_POST['suchstring'] . '%'; 
    ...->bind_param("s", $suchstr)


    nicht vergessen die Parameter zu übergeben.
    Und htmlspecialchars auch bei
    $row["AnlagenNr"]
    und
    $_POST['suchstring']

    also egtl IMMER. Es gibt nur wenige Ausnahmen, warum man einen String ohne Maskierung in HTML einbaut.

    Du hast ja immernoch
    $db->error()
    anstatt
    $db->error
    =)
    und den Fehler bei Zeile 12

    PHP
    echo"<table id=" *hier bricht der php string ab * myTable" class="tablesorter" rules="all">";


    entweder:

    PHP
    echo"<table id="myTable" class="tablesorter" rules="all">";


    EDIT: hier im Forum funktionieren keine Backslashes.
    Es sollte also ein maskiertes " mit einem Backslash zu sehen sein
    oder

    PHP
    echo"<table id='myTable' class='tablesorter' rules='all'>";

    Und das hier sieht "komisch" aus:

    PHP
    $db->query ($sql)


    besser

    PHP
    $db->query($sql) // leerzeichen weg

    und Augaben (selbst wenn sie aus der db kommen) maskieren: htmlspecialchars()
    zB:

    HTML
    <td>' . htmlspecialchars($row["AnlagenNr"]) . '</td>


    Denn "reverse injection" (Danke Basii =) wollen wir nicht.
    Wie soll das gehen?
    Duch Unaufmerksamkeit gelangt ein String "<script>alert(1)</script>" in die db auf zB Spalte `ProjektBezeichnung`.
    Beim unmaskierten Ausgeben auf der Seite wird der Inhalt (das Script) bei dem Betrachter ausgeführt!

    Was geht denn jetzt eigtl nicht?
    "Gibt nichts aus :s" gerade gesehen =)

    Du hast ->bind_param("...", ...) vergessen

    Keine Nutzerdaten in die Query! =)
    Du nutzt doch glaub ich mysqli. Dann nutz die Platzhalter ? um die POSTs einzufügen.
    Mit dem WHERE: das kannst Du wir in PHP schreiben:

    Code
    php: if( (this or that) and those and any )
    sql:
    WHERE (this or that) and those and any
    SQL
    SELECT * 
    FROM `tblAnlagenNr`
    WHERE 
    	(`AnlagenNr` LIKE ? OR `ProjektBezeichnung` LIKE ?) 
    	AND `Autr-Erh` = '1' 
    	AND `revidiert` = 'Nein' 
    	AND `aktiv` = 'Ja'


    die % musst Du noch über die parameter mitgeben.
    Also schickst Du nicht $_POST['suchstring'] als Parameter,
    sondern

    PHP
    ...
    $suchstr = '%' . $_POST['suchstring'] . '%'; 
    ...->bind_param("s", $suchstr)


    zu EDIT:
    dann halt:

    SQL
    AND `Autr-Erh` = ?


    und in php

    PHP
    ...
    $autr_erh = (int)(isset($_POST['Autr-Erh'])); 
    // (int) bewirkt, dass der Wert in eine Ganzzahl (integer) umgewandelt wird. 
    // (isset(...)) gibt true oder false zurück. also kann nur 0 oder 1 rauskommen
    ...->bind_param("i", $autr_erh) // "i" für int

    Naja, für solche Sachen nutzt man 0 und 1.
    Wenn es aber keine Massen an Daten in den Tabellen gibt (hunterttausende oder mio), dann kann man das auch mit ja/nein machen.
    Der Sinn dahinter ist, dann man nicht zB 3 mio mal "ja" in der Tabelle hat.

    Anderes Bsp wäre ein Ort. 1 mio Einträge haben das Feld "ort" mit "Berlin" belegt.
    Damit nicht 1 mio VARCHAR in den Tabellen auftauchen, kann man eine Detailtabelle anlegen.
    In der Detailtabelle steht dann zB "1" für "Berlin". Andere Tabellen können dann einen einfachen Zahlenwert nutzen.

    Aber das könn´ wir nicht komplett ausführen :D
    Kannst ja bei Gelegenheit hier rein sehen: http://www.php-kurs.com/daten-struktur…r-datenbank.htm

    zum Thema:
    Das schöne an HAVING ist, das man noch "Platz für WHERE" hat:

    Das ist dann der Einfache teil.
    Anstatt ON kann man USING nutzen, denn die Feldnamen haben den gleichen Namen.

    JOIN oder LEFT JOIN : wenn die AnlagenNr (der jeweilige Wert) immer in der 2ten Tabelle vorkommen muss,
    dann kannst Du JOIN verwenden.
    Die Tabelle heißt ja "tblAnlagenNr", daher denk ich ist das die Hauttabelle (wo die AnlagenNr her kommen).

    Mit JOIN bekommst Du immer nur einen Datensatz, wenn es die AnlagenNr auch in der Tabelle tblAnlagenNr gibt.
    Mit LEFT JOIN bekommst Du Daten der Tabelle tblServiceDaten auch dann, wenn es keine passende AnlagenNr in tblAnlagenNr gibt.

    BTW - wenn eine AnlagenNr in 2013 zB 3 mal vorkommt - willst Du dann alle 3 (Duplikate)? Oder nur einmal (denn es ist ja die gleiche - nur das Datum unterscheidet sich).
    Wenn Du die Duplikate möchtest, dann muss das GROUP BY `AnlagenNr` (Zeile 11) weg.

    Kommt darauf an, wie das Formular aufgebaut ist.
    Wenn ein POST Feld leer abgeschickt wird, ist es trotzdem gesetzt - aber leer (leerer String).
    Ich denke Du kommst besser, wenn Du Dir die Struktur erstmal übersichtlich aufbaust.
    Dann kannst Du die "Arbeit" einsetzen.

    Eventuell (hab so langsam den Verdacht) rennen wir voll aneinander vorbei :D

    tabelle: tblServiceDaten

    Code
    AnlagenNr   | ServiceDatum
    --------------------------
    1           | 2010-06-06
    1           | 2013-06-06
    1           | 2013-07-06
    1           | 2014-06-06


    mit der ersten Variante die ich Dir gegeben hatte hättest Du hier AnlagenNr 1 NICHT bekommen.
    Denn

    Bin ich da "zu weit gerannt"? :D

    Glaub nicht.
    Das hieße doch jetzt:
    Grundauswahl von 2013
    aber
    filtere die aus, die in 2014 vorkommen
    SELECT 2013 HAVING *die auswahl* NOT IN 2014

    Wenn ich Dich jetz richtig verstanden hab wilst Du ja
    SELECT 2013 HAVING *die auswahl* NOT IN < 2013 (also 2014 zugelassen)

    wäre dann

    SQL
    HAVING `AnlagenNr` NOT IN
    (
        SELECT
            `AnlagenNr`
        FROM
            `tblServiceDaten`
        WHERE
            `ServiceDatum` < '2013-01-01' 
    )

    EDIT:

    Zitat

    noch ein kleines ding, kann ich die ausgabe auch gleich zählen und aufliesten


    was auflisten und was zählen?

    zwecks: // $l_header UND $test werden beide auf "" gesetzt
    war nur n Hinweis =) Hätt ja sein können ...

    Auch nur ein Hinweis: $nmb->processed; Zeile 44 führt nix aus. Ist wie eine Anweisung: $a;

    Die ganze Struktur ist nicht Augenfreundlich =)
    Das Ganze mal eingerückt:


    sehe aber immernoch nicht durch, sorry

    Zitat

    Es sollen alle AnlagenNr von 2013 Angezeigt werden
    außer diejenigen die auch im 2014 erledigt wurden.

    alle Service die im Jahre 2013 erledigt wurden
    aber noch nicht im Jahre 2014 sollen angezeigt werden.


    oO?
    Entweder isses für mich zu spät oder ... :D

    Also sollen jetzt alle `AnlagenNr` angezeigt werden:
    alle die: `ServiceDatum` BETWEEN '2013-01-01' AND '2013-12-31'
    aber nicht: `ServiceDatum` BETWEEN '2014-01-01' AND '2014-12-31'

    Sollte als Ergebnis bringen:
    '2013-01-01' AND '2013-12-31'
    aber nicht vom Jahr 2014

    ... ich seh net mehr durch =)

    guck mal hier:

    Prinzip:

    EDIT: sory, copy paste Fehler:
    variante 3: muss heißen
    `ServiceDatum` > '2013-12-31'
    (NICHT '2013-01-01')

    Zitat

    und nicht in 2014 oder früher als 2013 vorkommt.


    =)
    Momentan solltest Du bekommen:
    alles was BETWEEN '2013-01-01' AND '2013-12-31'
    und dessen `AnlagenNr` NICHT außerhalb diesen Bereiches liegt.

    Wenn aber jetzt

    BETWEEN '2013-01-01' AND '2013-12-31'
    UND darüber hinaus
    ABER NICHT darunter

    als Ergebnis kommen soll,
    dann sollte HAVING glaub ich so aussehen:


    In der Subquery bei HAVING sind also die "no go"´s drin

    ich glaube du wolltest auf $l_header prüfen, anstatt auf $_POST['l_header']
    siehe Kommentare: