Simple wrapper for PHP mysqli connection I'm creating some web apis to sync some data between desktop/mobile apps.
Working on it, I wrote a simple wrapper for mysqli connection to simplify querying like:

// examples
$connection = new MySQLiConnection();

$connection-&gt;execute("INSERT INTO songs VALUES ('God\'s Plan', 'Drake')");
$connection-&gt;execute('INSERT INTO songs VALUES (?, ?)', 'Perfect', 'Ed Sheeran');

$result = $connection-&gt;getResult('SELECT * FROM songs');
var_dump($result);

$result = $connection-&gt;getResult('SELECT * FROM songs WHERE artist = ?', 'Ed Sheeran');
var_dump($result);



Code of the wrapper:

&lt;?php

final class MySQLiConnection
{
    private $connection;

    public function __construct()
    {
        $this-&gt;connection = new mysqli('localhost', 'id', 'password', 'database');

        if ($this-&gt;connection-&gt;connect_errno)
        {
            throw new mysqli_sql_exception('Failed to open mysqli connection: ' . $this-&gt;connection-&gt;connect_error);
        }

        if (!$this-&gt;connection-&gt;set_charset('utf8'))
        {
            throw new mysqli_sql_exception('Failed to set utf8 character set: ' . $this-&gt;connection-&gt;error);
        }
    }

    public function __destruct()
    {
        if (!$this-&gt;connection-&gt;close())
        {
            throw new mysqli_sql_exception('Failed to close mysqli connection: ' . $this-&gt;connection-&gt;error);
        }
    }

    private function buildStatement(string $sql, ...$params) : mysqli_stmt
    {
        if ($statement = $this-&gt;connection-&gt;prepare($sql))
        {
            if (!empty($params))
            {
                $types = '';

                foreach ($params as $param)
                {
                    if (is_int($param))
                    {
                        $types .= 'i';
                    }
                    elseif (is_float($param))
                    {
                        $types .= 'd';
                    }
                    elseif (is_string($param))
                    {
                        $types .= 's';
                    }
                    else
                    {
                        $types .= 'b';
                    }
                }

                $statement-&gt;bind_param($types, ...$params);
            }

            return $statement;
        }

        throw new mysqli_sql_exception('Failed to prepare mysqli_stmt: ' . $this-&gt;connection-&gt;error);
    }

    public function execute(string $sql, ...$params) : void
    {
        $statement = $this-&gt;buildStatement($sql, ...$params);

        $success = $statement-&gt;execute();
        $statement-&gt;close();

        if (!$success)
        {
            throw new mysqli_sql_exception('Failed to execute mysqli_stmt: ' . $this-&gt;connection-&gt;error);
        }
    }

    public function getResult(string $sql, ...$params) : mysqli_result
    {
        $statement = $this-&gt;buildStatement($sql, ...$params);

        $success = $statement-&gt;execute();

        if ($success)
        {
            $result = $statement-&gt;get_result();

            $statement-&gt;close();
            return $result;
        }

        $statement-&gt;close();
        throw new mysqli_sql_exception('Failed to execute mysqli_stmt: ' . $this-&gt;connection-&gt;error);
    }
}

?&gt;
Error reporting
This is much better than your initial attempt, but somehow it become more verbose and I'll tell you why: you are, so to say, abused the error reporting. mysqli_sql_exception is not intended to be thrown manually. Like it's said in the article I gave you link to, mysqli can throw exceptions by itself - so just let mysqli to do it. And therefore there is no reason to check for errors manually, and it will greatly reduce the amount of repeated code.
Automatic binding
Your automated binding function is rather confusing. Apparently you didn't have any clear idea what this code is intended to do:
                else
                {
                    $types .= 'b';
                }

And I'll tell you - there is no good scenario for it.
Don't write a code just in case, when you just don't know what to write. In such circumstances just don't write any code at all.
Besides, I am scared by functions like this that sniff a type off a variable automatically. If you will have a query that will compare a string stored in a database to a number, the consequences would be fatal. And PHP is known for converting strings to numbers on its own will. So, although quite small, but there is still a possibility that your binding function will ruin your database.
That's why I prefer either a manual explicit type setting or just blunt setting a string type for all variables. At least it won't harm anyone.
In practice it means that I recommend to use string type for all parameters by default, with a possibility of a fallback with manual type setting. So, taken from ny other article, the mysqli binding function would be like
public function execute(string $sql, $params = [], $types = '') : mysqli_stmt
{
    if (!$params) {
        return $this-&gt;connection-&gt;query($sql);
    }
    $types = $types ?: str_repeat(&quot;s&quot;, count($params));
    $statement = $this-&gt;connection-&gt;prepare($sql);
    $statement-&gt;bind_param($types, ...$params);
    $stmt-&gt;execute();
    return $stmt;
}

this function does a lot of good stuff:

it lets you to run a query that doesn't support prepared statements

it does no harm automatically sniffing the variable's type

at the same time it lets you to provide a type string manually, like
  $conn-&gt;execute('INSERT INTO songs VALUES (?, ?, ?)', ['foo', 'bar', 1], &quot;ssi&quot;);



I also removed an argument unpacking operator from the function definition as it makes no sense. Trust me, it takes no effort to add two square brackets around parameters, but it makes your code WAY cleaner and more maintainable.
Closing statements
PHP is incredibly programmer-friendly. Resources don't have to be closed manually, they will be closed automatically when no longer needed.
The code
&lt;?php

final class MySQLiConnection
{
    public $connection;

    public function __construct($config)
    {
        mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
        try {
            $this-&gt;connection = new mysqli('localhost', 'id', 'password', 'database');
            $this-&gt;connection-&gt;set_charset('utf8');
        } catch (\mysqli_sql_exception $e) {
            throw new \mysqli_sql_exception($e-&gt;getMessage(), $e-&gt;getCode());
        }
    }

    public function execute(string $sql, $params = [], $types = '') : mysqli_stmt
    {
        if (!$params) {
            return $this-&gt;connection-&gt;query($sql);
        }
        $types = $types ?: str_repeat(&quot;s&quot;, count($params));
        $statement = $this-&gt;connection-&gt;prepare($sql);
        $statement-&gt;bind_param($types, ...$params);
        $statement-&gt;execute();
        return $statement;
    }

    public function getResult(string $sql, $params = [], $types = '') : mysqli_result
    {
        return $this-&gt;execute($sql, $params, $types)-&gt;get_result();
    }
}

As you can see, the final code is much more concise, thanks to code reuse and proper error reporting.
I also made $connection variable public, as there is no more getConnection() function but you will need to access it from outside for sure.
An exception is caught in the constructor because of security concerns. A stack trace for the exception thrown in case of a connection error would contain the database credentials that will end up either in the server logs or - the worst case - displayed on the screen. Once re-thrown this way, credentials are no more exposed. So although as a rule you don't catch an Exception right in place, here it's a special case to take care of.