Skip to main content
2 of 3
Added the code of the Config class.

PHP MySQLi Wrapper Class For API

I've never had my code reviewed before, so I am taking this opportunity to see what can be improved with my coding style. I am still somewhat "new" to PHP, in the sense that I'm an old dog finally learning new tricks.

Here is a MySQLi wrapper I've coded in PHP. Overall, this code is part of an API project. I'm making an API in PHP which I will use for my WPF Desktop Application for the Database Models.

Here is Class.Config.php:

<?php

namespace Database;

use Exceptions\DatabaseException;
use Exceptions\ConnectionException;

class Config
{
    /**
    * @var string $host The connection host.
    * @var string $username The connection username.
    * @var string $password The connection password.
    * @var string $dbname The connection database name.
    */
    private string $host, $username, $password, $dbname;

    /**
    * @var \mysqli $conn The mysqli connection object.
    */
    private \mysqli $conn;

    /**
    * Configures the Config object with the default connection string.
    *
    * @param array $config The config array, expects indexes of: host, username, password, and dbname.
    * @param mysqli $connection The optional mysqli connection object.
    */
    public function __construct(?array $config = null, \mysqli $connection = null)
    {
        if ($connection !== null && !($connection instanceof \mysqli))
        {
          throw new \InvalidArgumentException(
            "Invalid mysqli connection object passed. Instance of 'mysqli' expected, '" . gettype($connection) . "' given."
          );
        }

        if ($config === null)
        {
            // Retrieve an array of the connection data from the database.ini file.
            $config = parse_ini_file("database.ini");
        }

        // Retrieve the database credentials from the parsed file
        foreach($config as $key => $value)
        {
          $this->$key = $value ?? null;
        }

        // Verify that the config file properly sets up the variables.
        try
        {
            if (!isset($this->host, $this->username, $this->password, $this->dbname)
                || empty($this->host)
                || empty($this->username)
                || empty($this->password)
                || empty($this->dbname))
            {
                throw new \InvalidArgumentException("Connection string expects values for ['host', 'username', 'password', 'dbname']");
            }
        }
        catch (\InvalidArgumentException $e)
        {
            error_log("Missing Configuration Index: {$e}");
            throw $e;
        }

        // Create a new MySQLi connection
        $this->conn =
            $connection ??
            new \mysqli(
                $this->host,
                $this->username,
                $this->password,
                $this->dbname
            );
    }

    /**
    * Validates the connection.
    *
    * @return mysqli The mysqli connection object.
    */
    public function connect()
    {
        try
        {
            /**
            * Checks to see if there are any errors with the validated connection string.
            *
            * @var \mysqli $this->conn The connection object configured in the constructor.
            */
            if ($this->conn->connect_error)
            {
                throw new ConnectionException(
                  $this->conn->connect_error
                );
            }
            else
            {
                return $this->conn;
            }
        }
        catch (ConnectionException $e)
        {
            error_log("Connection Error: " . $e->getMessage());
            throw $e;
        }
        catch (DatabaseException $e)
        {
            error_log("Database Error: " . $e->getMessage());
            throw $e;
        }
        catch (Exception $e)
        {
            error_log("Error: " . $e->getMessage());
            throw $e;
        }
    }
}
?>

Here is class.MySQLi.php:

<?php
namespace Database;

use Database\Config;

class MySQLi
{

  /**
  * @var \mysqli $conn The mysqli connection object.
  */
  private \mysqli $conn;

  /**
  * Initialize the connection between the database config.
  */
  public function __construct()
  {
      $conn = new Config;
      $this->conn = $conn->connect();
  }

  /**
  * Method for getting the connection variable.
  *
  * @return \mysqli | bool $this->conn The mysqli object configured in the constructor.
  */
  public function getConnection() : \mysqli | bool
  {
    return $this->conn;
  }

  /**
  * Close the connection once the script ends.
  */
  public function __destruct()
  {
      $this->conn->close();
  }

  /**
  * Build a parameterized query to protect against SQL injections by default.
  *
  * @param string $query The SQL query string with placeholders for parameters.
  * @param mixed ...$params The array? of parameters to be bound to the query.
  * @return \mysqli_result|bool Returns the result of the query or false if an error is encountered.
  */
  public function Query(string $query, ...$params) : \mysqli_result | bool
  {
      // Prepare the SQL statement or trigger an error if preparation fails.
      $statement = $this->getConnection()->prepare($query) or trigger_error($this->getConnection()->error, E_USER_ERROR);

      if (!$statement) return false;

      // Build the parameters if provided.
      if(!empty($params))
      {
          $types = [];
          $bindParams = [];

          foreach ($params as $param)
          {
              // Get the type of each parameter: i, d, s, or b.
              $types[] = $this->getParamType($param);
              $bindParams[] = $param;
          }

          try
          {
              $types = implode("", $types);
              $statement->bind_param($types, ...$bindParams);
          }
          catch (\ArgumentCountError $e)
          {
              return false;
          }
      }

      // Execute the statement and return the result if successful
      if ($statement->execute())
      {
          $result = $statement->get_result();
          $statement->close();
          return $result;
      }

      return false;
  }

  /**
  * Insert rows of data into a database table.
  *
  * @param string $query The SQL query string with placeholders for parameters.
  * @param mixed ...$params The array? of parameters to be bound to the query.
  */
  public function Insert(string $query, ...$params)
  {
  // Still debating usage.
  }

  /**
  * Fetch rows of data from a database table.
  *
  * @param string $query The SQL query string with placeholders for parameters.
  * @param mixed ...$params The array? of parameters to be bound to the query.
  * @return array|false Returns an array of rows on success, or false on failure.
  */
  public function Rows(string $query, ...$params) : array | bool
  {
    if($result = $this->Query($query, ...$params))
    {
        $rows = [];

        while ($row = $result->fetch_assoc())
        {
            $rows[] = $row;
        }

        return $rows;
    }

    return false;
  }

  /**
 * Get the parameter type for binding in a prepared statement.
 *
 * @param mixed $param The parameter value.
 * @return string Returns the parameter type character.
 */
  private function getParamType($param) : string
  {
      if (is_int($param))
      {
          return "i";
      }
      elseif (is_float($param))
      {
          return "d";
      }
      elseif (is_string($param))
      {
          return "s";
      }
      else
      {
          return "b";
      }
  }
}
?>

Usage

Simplify Fetching Rows From Database

With my wrapper class, I can simply do this:

// Get the database connection
$conn = new MySQLi;

// SQL query to select all data from the table
$query = "SELECT * FROM structure_ranks";

// Fetch all rows from the result set and store them in the array
$rows = $conn->Rows($query);

// Output the array as JSON
echo json_encode($rows, JSON_PRETTY_PRINT);

As opposed to doing this:

// Create an instance of the Config class
$config = new Config();

// Get the database connection
$conn = $config->connect();

// Check the connection
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}

// SQL query to select all data from the table
$sql = "SELECT * FROM structure_ranks";

// Execute the query
$result = $conn->query($sql);

// Fetch all rows from the result set and store them in the array
$array = [];
if ($result->num_rows > 0) {
    while ($row = $result->fetch_assoc()) {
        $array[$row['id']] = $row;
    }
}

// Close the connection
$conn->close();

// Output the array as JSON
echo json_encode($array, JSON_PRETTY_PRINT);