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);