Uncaught TypeError: Typed property xyz::$result must be an instance of mysqli_result or null, bool used

Viewed 393

I have the following shortened PHP 7.4 code

class xyz extends abc {

    protected ?mysqli_result $result = null;
    //some more class properties..

    public function queryExecute(string $query): void {        
            $this->result = $this->mysqli->query($query);
            //some more doings..        
    }
}

when I call the function with a valid query e.g. Select id from test a valid result is returned and no error occurs.

If I, for example, execute an "invalid" query Select id from test2 (table does not exist) the mysqli->query returns a bool and I get the error:

Fatal error: Uncaught TypeError: Typed property xyz::$result must be an instance of mysqli_result or null, bool used in xyz.php on line 107 TypeError: Typed property xyz::$result must be an instance of mysqli_result or null, bool used in xyz.php on line 107

Is there some possibility to get the properties to accept bool and mysqli_result type in one? Mixed is no valid keyword so I am a little bit clueless right now how to get around this.

2 Answers

You can wrap this in a function like this (or modify your existing function):

public function queryExecute(string $query): void
{
    $result = $this->mysqli->query($query);

   if (!$result) {
       // Handle error eg: Throw Exception
       // In case you want to ignore the error (not recommended)
       // you want to place a 'return;' here
   }

   $this->result = $result;
}

In PHP8 you wont have this problem anymore. Due to union-types, which are likely to come.

In case you are using an IDE like PhpStorm you can do this aswell, to get intellisense:

/** @var null|bool|mysqli_result */
protected $result = null;

In case you are trying to build an abstraction-layer for mysqli, you may want to have a look at PDO.

Not until PHP 8 is released. It will be possible with PHP 8 to have union types, but that doesn't mean it is a good idea.

First of all, you should always enable proper mysqli error reporting. Just add this line before new mysqli():

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

If a query fails then mysqli will throw an exception instead of returning FALSE value.

However, mysqli_query() can also return TRUE for queries that do not return any results e.g. INSERT.

Since PHP 8 you will be able to do union types:

class xyz extends abc {

    protected mysqli_result|bool $result;

    public function queryExecute(string $query): void {        
        $this->result = $this->mysqli->query($query);  
    }
}

In PHP 7.4 you can do this by replacing true with null. After all, you really don't need that boolean value.

class xyz extends abc {

    protected ?mysqli_result $result;

    public function queryExecute(string $query): void {        
        $res = $this->mysqli->query($query);
        $this->result = true === $res ? null : $res;
    }
}

How to write mysqli abstraction library?

It looks like you are trying to build a mysqli abstraction library. PHP already has a much better library called PDO, but if you want to build your own here are some pointers.

  1. Use prepared statements all the time. There's barely any reason to use mysqli_query.

  2. Keep it simple. Don't bother with storing mysqli_result in an attribute. You only need this object to fetch results and then you can discard it.

  3. Always enable error reporting.

  4. Don't expose mysqli_result nor mysqli_stmt objects. Use them only internally.

I have used this class for demonstration purposes how you can start with such library.

class DBClass extends mysqli {
    public function __construct(
        $host = null,
        $username = null,
        $passwd = null,
        $dbname = null,
        $port = null,
        $socket = null
    ) {
        mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
        parent::__construct($host, $username, $passwd, $dbname, $port, $socket);
        $this->set_charset('utf8mb4');
    }

    /**
     * Executed prepared statement
     *
     * @param string $sql SQL query with placeholders e.g. SELECT * FROM users WHERE Id=?
     * @param array $params An array of parameters to be bound
     * @return array|null
     */
    public function safeQuery(string $sql, array $params = []): ?array {
        // prepare/bind/execute
        $stmt = $this->prepare($sql);
        if ($params) {
            $stmt->bind_param(str_repeat("s", count($params)), ...$params);
        }
        $stmt->execute();
        // If the query produces results then fetch them into multidimensional array
        if ($result = $stmt->get_result()) {
            return $result->fetch_all(MYSQLI_BOTH);
        }
        // return nothing if the query was successfully executed and it didn't produce results
        return null;
    }
}

There is no need to store the result in an attribute because we return the array with values immediately. This class fulfils most use cases and is very simple to use.

$db = new DBClass('localhost', 'user', 'pass', 'db_name');

// INSERT
$db->safeQuery('INSERT INTO user(name) VALUES(?)', ['Dharman']);

// You still have access to the parent mysqli object if needed
var_dump($db->insert_id);

//SELECT
$usernames = $db->safeQuery('SELECT * FROM user WHERE name=?', ['Dharman']);

foreach ($users as $user) {
    echo $user['id'];
}
Related