Avatar billede jakobdo Ekspert
14. februar 2014 - 20:38 Der er 16 kommentarer og
1 løsning

Overskrive mysqli_stmt class

Hej,
i forbindelse med jeg skulle tilføje "query logging" til en eksisterende app.
Fik jeg ideen.

Jeg extender da bare mysqli og så er den prut slået.
Som sagt så gjort.

class mydb extends mysqli{
  public function __construct($host,$user,$pass,$db, $port = null, $socket = null){
    parent::__construct($host, $user, $pass, $db, $port, $socket);
  }

  public function query($query){
    $this->log($query);
    return @parent::query($query);
  }

  public function prepare($query){
    $this->log($query);
    return parent::prepare($query);
  }

  private function log($query){
    $filename = "sql.log";
    file_put_contents($filename, $query . "\n", FILE_APPEND);
  }
}

Det virker "perfekt" på query, hvor jeg jo får den fulde sql med.
Men prepare giver jo kun:

SELECT * FROM table WHERE id = ?

Så jeg kommer til at mangle de "værdier" som jeg senere anvender i bind_param kaldet.

Har kigget på nogle løsninger, hvor de forsøger at overskrive bind_param(), men det kunne jeg ikke finde ud af. :o(

Samtidig har jeg læst lidt på at aktivere query logging på mysql-niveau. Men det synes jeg umiddelbart til kun at virker på hele serveren eller ej.

Samtidig ønsker jeg også info om: Hvilken bruger har rent faktisk lavet omtalte query, tidspunkt, ip osv...
Avatar billede arne_v Ekspert
14. februar 2014 - 21:01 #1
Foerst er der et filosofisk problem du skal tage stilling til.

:-)


$stmt = $con->prepare('SELECT * FROM table WHERE id = ?');
$stmt->bind_param('i', 123);
$stmt->execute();
...
$stmt->bind_param('i', 456);
$stmt->execute();
...


sender ikke:

SELECT * FROM table WHERE id = 123;
SELECT * FROM table WHERE id = 456;

til databasen - der sendes:

PREPARE stmt FROM 'SELECT * FROM table WHERE id = ?';
SET @v = 123;
EXECUTE stmt USING @v;
SET @v = 456;
EXECUTE stmt USING @v;

Saa hvad vil du have logget?

:-)
Avatar billede jakobdo Ekspert
14. februar 2014 - 23:10 #2
Jeg vil bare gerne logge "alt". Insert Update delete forespørgsler.
Avatar billede arne_v Ekspert
16. februar 2014 - 02:17 #3
Det var jo ikke spoergsmaalet.

Spoergsmaalet er om du vil logge PREPARE i prepare, logge SET i bind_param og logge EXECUTE i execute eller om du forventer at faa logget en ikke eksisterende SQL (SELECT/INSERT/DELETE/UPDATE) med ? erstattet med vaerdier.
Avatar billede jakobdo Ekspert
16. februar 2014 - 13:25 #4
Jeg kan jo ligesom godt fornemme, at det er svært at logge en ikke eksisterende sql.
Så hvis det er muligt, ville en logning af:
prepare og bind_param være ok.

Bare jeg kan grave frem, hvem der har gjort hvad...
Og det er simpelthen bare fordi jeg er doven og ikke gider kode en "linje" log hver sted der laves kald til databasen.

Jeg ville forsøge at overskrive query / prepare på en extended klasse af mysqli.
Avatar billede arne_v Ekspert
16. februar 2014 - 16:06 #5
Det kraever bare lidt mere arbejde at faa de to paa plads.

Da new ikke laves i din kode kan du ikke bare lave en klasse som extender mysqli_stmt men bliver noedt til at lave en klasse som delegater d.v.s. et private field med den originale mysqli_stmt og saa forwarding implementation af alle metoder og evt. noget for de public felter ogsaa.
Avatar billede jakobdo Ekspert
16. februar 2014 - 19:29 #6
Og det er jo netop derfor jeg har oprettet dette spørgsmål Arne. :o)

Hjælp mig.
Link eller lign...

Extend mysqli_stmt er meget mangelfuld, når man googler... :)
Avatar billede arne_v Ekspert
16. februar 2014 - 20:06 #7
Jeg proever og lave et eksempel.
Avatar billede jakobdo Ekspert
16. februar 2014 - 20:20 #8
Jeg venter da bare så. :o)
Avatar billede arne_v Ekspert
16. februar 2014 - 21:46 #9
Det tog 10 minutter at lave det egenetligt og 60 minutter at faa value/reference til at virke (og jeg fik det ikke engang til at virke paa en god maade - men func_get_args goer ikke det som jeg synes at den skal ifoelge docs).

Jeg valgte at lave baade mysqli og mysqli_stmt som delegating class. mysqli_stmt er noedt til at vaere det og saa synes hef at mysqli skal laves paa samme maade.

Der skal aabenlyst tilfoejes flere metoder, men princippet skulle vaere klart.

Kode:


<?php
class log_mysqli_stmt {
    private $real;
    public function __construct($real) {
        $this->real = $real;
    }
    public function bind_param($typinf, &$v1, &$v2 = null, &$v3 = null, &$v4 = null, &$v5 = null, &$v6 = null, &$v7 = null, &$v8 = null, &$v9 = null, &$v10 = null) {
        echo "bind: ";
        for($i = 0; $i < func_num_args(); $i++) echo ' ' . func_get_arg($i);
        echo "\r\n";
        // this should work as I read the docs, but ot does not so work around below
        // call_user_func_array(array($this->real, 'bind_param'), func_get_args());
        $refarr = array_slice(array($typinf, &$v1, &$v2, &$v3, &$v4, &$v5, &$v6, &$v7, &$v8, &$v9, &$v10), 0, func_num_args());
        call_user_func_array(array($this->real, 'bind_param'), $refarr);
    }
    public function execute() {
        $this->real->execute();   
    }
    public function store_result() {
        $this->real->store_result();   
    }
    public function bind_result(&$v1, &$v2 = null, &$v3 = null, &$v4 = null, &$v5 = null, &$v6 = null, &$v7 = null, &$v8 = null, &$v9 = null, &$v10 = null) {
        // this should work as I read the docs, but ot does not so work around below
        // call_user_func_array(array($this->real, 'bind_result'), func_get_args());   
        $refarr = array_slice(array(&$v1, &$v2, &$v3, &$v4, &$v5, &$v6, &$v7, &$v8, &$v9, &$v10), 0, func_num_args());
        call_user_func_array(array($this->real, 'bind_result'), $refarr);
    }
    public function fetch() {
        $res = $this->real->fetch();
        echo "fetch: " . ($res ? "true" : "false") . "\r\n";
        return $res;   
    }
    public function close() {
        $this->real->close();
    }
}

class log_mysqli {
    private $real;
    public function __construct($host, $un, $pw, $db) {
        $this->real = new mysqli($host, $un, $pw, $db);   
    }
    public function prepare($sql) {
        echo "prepare: $sql\r\n";
        return new log_mysqli_stmt($this->real->prepare($sql));
    }
    public function close() {
        $this->real->close();
    }
}

// some database code
$con = new log_mysqli('localhost', 'root', '', 'Test');
if(mysqli_connect_errno()) die(mysqli_connect_error());
if($stmt = $con->prepare('SELECT f1,f2 FROM t1 WHERE f1 > ?')) {
    $stmt->bind_param('i', $cutoff);
    $cutoff = 5;
    $stmt->execute();
    $stmt->store_result();
    $f1 = 0;
    $f2 = '';
    $stmt->bind_result($f1, $f2);
    while ($stmt->fetch()) {
        print $f1 . ' ' . $f2 . "\n";
    }
    $stmt->close();
} else {
    die($con->error);
}
$con->close();
?>

Avatar billede jakobdo Ekspert
16. februar 2014 - 21:54 #10
Cool Arne_V.
Tester den i morgen.
Smid et svar, så kan vi lukke, når jeg har fået testet. :o)
Avatar billede arne_v Ekspert
16. februar 2014 - 22:00 #11
svar
Avatar billede jakobdo Ekspert
02. marts 2014 - 10:57 #12
Hej Arne,
så fik jeg (dovnehund) endelig testet din kode.

Skal:

class log_mysqli {

ikke være:

class log_mysqli extends mysqli{ ??

For ellers kender den jo ikke alle de bagvedliggende metoder?
Avatar billede jakobdo Ekspert
02. marts 2014 - 11:24 #13
Nu skriver du jo faktisk selv, delegating class, så gætter svar på mit tidligere spørgsmål er nej, den skal ikke extend mysqli.

Men når jeg tester din kode "rent" får jeg disse "fejl":

prepare: SELECT f1,f2 FROM t1 WHERE f1 > ? bind: i
Warning: call_user_func_array() expects parameter 1 to be a valid callback, first array member is not a valid class name or object in /var/www/inc/mysql.inc.php on line 17

Fatal error: Call to a member function execute() on a non-object in /var/www/inc/mysql.inc.php on line 21
Avatar billede arne_v Ekspert
03. marts 2014 - 02:11 #14
PHP's mangel paa type check goer at man ikke behoever at extende.
Avatar billede arne_v Ekspert
03. marts 2014 - 02:12 #15
Det er jo et koerende eksempel. Men teknikken er nok lige til kanten af det stabile.

Jeg har testet med 5.4.16 - hvad bruger du?
Avatar billede arne_v Ekspert
04. marts 2014 - 03:47 #16
Fik du det til at virke hos dig?
Avatar billede jakobdo Ekspert
04. marts 2014 - 08:31 #17
Nej, jeg opgav. Er en doven hund.
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Vi tilbyder markedets bedste kurser inden for webudvikling

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester