0

I am looking for a way to build a handler or edit a php object, the following is an example for mysqli

I have a system with many files using the mysqli object in a variable

$my_var=new mysqli($host, $user, $pass, $base);

Files call this to do queries and get the results like this:

$q=$my_var->query("SELECT * FROM table");
$q->fetch_assoc();

if there is an error on the query, $my_var->error will be populated on the first line and will throw an error 500 on the second line.

I am looking for a way to build a handler for $my_var->error and throw the error before any fetch_* method is called, so,

is there any way to build a handler/listener for $my_var->error?

if this is not possible, is there any way to override mysqli fetch_assoc(), fetch_row() and fetch_array() methods to check if $my_var->error is true and show the error before continue?

I know about try{}catch(), throw new Exception and or die() methods, but these mean to edit every fetch_* in the system, I would like to do it editing the connection file only.

Thank you!

---editing---

I think prepared statements are not what I am looking for.

---editing 2---

And no, I am not looking how to get mysqli errors.

Thank you for your help fyrye!

---Answer Final Code---

class mysqli2{
    private $c;
    function __construct(...$args){ // open connection
        $this->c=new mysqli(...$args);
        if(!empty($this->c->error)){ // check for errors
            echo("<script>console.error(\"MySQL: ".$this->c->error."\");</script>");
        }
    }
    function query($query, $resultmode = MYSQLI_STORE_RESULT){
        $r=$this->c->query($query, $resultmode); // make query
        if(!empty($this->c->error)){ // check for errors
            echo("<script>console.error(\"MySQL: ".$this->c->error."\");</script>");
        }
        return $r; // returns query results
    }
    function __call($method, $args){ // calls others mysqli methods
        return $this->c->$method(...$args);
    }
    function __get($name){ // get all the properties
        return $this->c->$name;
    }
    function __set($name, $value){ // set all the properties
        if (property_exists($this->c, $name))$this->c->$name = $value;
    }
}
stramin
  • 2,183
  • 3
  • 29
  • 58
  • 1
    You would need to create a wrapper or database abstraction layer (DBAL) for your mysqli object, that can handle every method that `mysqli` does. I suggest looking into other preexisting and maintained libraries like Doctrine or Eloquent before writing your own. – Will B. Oct 29 '18 at 21:24
  • Are you want to have a posabbol;ity to accumulate an exeptions with inside a loop? – Николай Лубышев Oct 29 '18 at 21:46
  • 1
    @fyrye If you feel strongly about that, you should ping Machavity using `@Machavity` just as I did for you here. They'll get the ping and if they agree, they'll reopen. – Funk Forty Niner Oct 29 '18 at 22:55
  • 2
    Thanks @FunkFortyNiner - @Machavity this question is not related to either of the questions marked as duplicate. The OP is asking how to override the default functionality of `mysqli::query` in order to forcibly throw an exception early, without explicitly handling the exceptions. Didn't think pings inside of the code blocks worked. – Will B. Oct 29 '18 at 22:59

1 Answers1

1

To suggest a best practice, when using either PDO or MySQLi extensions, it is suggested to always check the return results from any of the usable methods, before moving on to a method that relies on its result.

As mysqli::query returns false on an error, it should be checked before using fetch_assoc, instead of assuming it is not false. The same applies to using fetch_assoc, as it can return NULL;

if (!$q = $my_var->query($sql)) {
    //something went wrong - handle the error here.
}
if ($data = $q->fetch_assoc()) {
   echo $data['column'];
}

However as I suggested, you would need to create an abstraction layer.

Since $q would be false on error, the exception from the code in your question would be:

Fatal error: Call to a member function fetch_assoc() on boolean

Meaning you would not be able to override the fetch_* methods, without overriding mysqli::query to always return an object.

Please do not use in production code.

This is only an example of how to override the mysqli::query method with your own. You would also need to override all other mysqli::*** methods, like prepare, commit, etc.

Example https://3v4l.org/PlZEs

class Conn
{

    private $conn;

    public function __construct(...$args) 
    {
        $this->conn = new mysqli(...$args);
    }

    public function query($query, $resultmode = \MYSQLI_STORE_RESULT) 
    {
       $d = $this->conn->query($query, $resultmode);
       if (!empty($this->conn->error)) {
           throw new \RuntimeException($this->conn->error);
       }

       return $d;
    }
}

//Your connection code
$con = 'my_var';
$$con = new Conn('host', 'user', 'pass', 'base');

//example usage of valid query
$q = $my_var->query('Valid Query');
$q->fetch_assoc();

//example use of invalid query throwing an exception before `fetch_assoc`
$q = $my_var->query('This is not valid');
$q->fetch_assoc();

Results

I was successful
-----

Fatal error: Uncaught exception 'RuntimeException' with message 'Expected "Valid Query"' in /in/PlZEs:51
Stack trace:
#0 /in/PlZEs(70): Conn->query('This is not val...')
#1 {main}
  thrown in /in/PlZEs on line 51

This approach uses PHP 5.6 argument packing and unpacking, to match the function call arguments, to prevent having to manually define them.

It is possible to expand on this to write a message to log file, send an email, trigger an event, or display a friendly error message instead of throwing an exception. As well as overriding the mysqli_stmt responses with your own Statement object(s).

Will B.
  • 17,883
  • 4
  • 67
  • 69
  • Tested and worked perfectly, I iterated for every variables and methods inside mysqli object and used `func_get_args` to get all their arguments, works as a charm, building an abstraction layer as a secondary class was a good idea – stramin Oct 30 '18 at 13:27
  • 1
    @stramin if you use PHP 5.6 or higher, using the splat operator, `...$args` for the method argument and pass it to the overridden `$this->conn->method(...$args)` as I did in the example `__construct` should be less overhead than using `func_get_args()`. Alternatively in PHP 5.5 - 5.3, you can use `$r = call_user_func_array(array($this->conn, 'method'), func_get_args());` To use `func_get_args()` on `new` instances of an object, you can use a `ReflectionClass::newInstanceArgs` like https://3v4l.org/5kgkN (compatible for PHP 5.3+) – Will B. Oct 30 '18 at 14:25
  • 1
    @stramin if you don't want to have to manually define every `mysqli_query::method` to override, you can instead use the [`__call` magic method](http://php.net/manual/en/language.oop5.overloading.php#object.call). Like so https://3v4l.org/6U6dt – Will B. Oct 30 '18 at 14:35
  • Incredible, I just applied both, `...` operator and `__call`, this reduced my code in 41 lines, thank you again, I wonder if I can post my code in the question. – stramin Oct 30 '18 at 17:49
  • 1
    @stramin yes you can update your question to show what you ended up adopting from my answer, but leave out any sensitive information that might expose your application to security vulnerabilities. Which will help the duplicate question flags as well. – Will B. Oct 30 '18 at 17:56
  • 1
    @stramin you can improve the performance further by replacing `call_user_func_array(array($this->c, $method), $args);` By using `$this->c->$method(...$args);` Also instead of defining the abstract layer (AL) properties, you can use `__get` and `__set` like: https://3v4l.org/AeZAT to retrieve or set the desired values if they don't exist in your AL. – Will B. Oct 30 '18 at 20:24
  • Perfect, the code is short, I used it on other others systems and worked perfectly, I edited the question to add these changes, thank you! – stramin Oct 31 '18 at 14:37