I have just started learning the concept of Object oriented programming and I've written this class with functions.
It works fine, but I'm interested in knowing if I have done this correctly...
Here is my code:
class Database{
const DB_HOSTNAME = 'localhost';
const DB_USERNAME = 'root';
const DB_PASSWORD = 'password';
const DB_NAME = 'shop';
protected $_db_connect;
protected $_sql;
protected $_result;
protected $_row;
function db_connect(){
$this->_db_connect = mysql_connect(self::DB_HOSTNAME,self::DB_USERNAME,self::DB_PASSWORD) or die(mysql_error());
}
function slect_db(){
mysql_select_db(self::DB_NAME) or die(mysql_error());
}
function sql(){
$this->_sql = 'SELECT * FROM users';
}
function query(){
$this->_result = mysql_query($this->_sql);
}
function fetch_array(){
while($this->_row = mysql_fetch_array($this->_result)){
$username = $this->_row['user_USERNAME'];
echo "<ul>";
echo "<li>".$username."</li>";
echo "</ul>";
}
}
function db_close(){
mysql_close($this->_db_connect);
}
}
$database = new Database();
$database->db_connect();
$database->slect_db();
$database->sql();
$database->query();
$database->fetch_array();
$database->db_close();
Please use PDO or MySQLi as it's more secure and mysql_* functions are deprecated as stated above, I've provided some generic code using PDO to help you with that new venture. As stated in the comments, you really shouldn't be echoing out the data if you're looking for an object oriented design, what you should be doing is after you do the query, return the rows that you fetched and from there use something such as a foreach loop to then display your data. Doing your database class this way will also make sure that multiple database connections aren't open at once! Note, this code is just for you to reference, and should be tested before use in production or anything live.
config.php:
<?php
//Enter your database connection details here.
$host = 'localhost'; //HOST NAME.
$db_name = 'databasename'; //Database Name
$db_username = 'root'; //Database Username
$db_password = ''; //Database Password
try
{
$pdo = new PDO('mysql:host='. $host .';dbname='.$db_name, $db_username, $db_password);
}
catch (PDOException $e)
{
exit('Error Connecting To DataBase');
}
?>
database.class.php:
<?php
class database
{
function __construct($pdo)
{
$this->pdo = $pdo;
}
function getData()
{
$query = $this->pdo->prepare('SELECT * FROM database');
$query->execute();
return $query->fetchAll();
}
}
?>
index.php:
<?php
require_once 'config.php';
require_once 'database.class.php';
$db = new database($pdo);
$rows = $db->getData();
?>
It is possible to improve the way you connect to databases using autoloading and dependency injection containers. Here is a way of using Auryn to connect to your database while being sure there is only one connection opened and not having to manually require files throughout your application.
I will cover only PDO and Auryn here. There are other dependency injection containers and notably the mysqli extension to connect to database, but the content should help you using another container if you wish to do so.
The database class
Having a database class is superfluous. The \PDO class is already providing all necessary methods to query the database. Having a database class makes you repeat the functions it provides and limits your actions (or makes you create many functions) when you want to for example use multiple different fetch styles depending on your needs in a specific method.
Dependency Injection
If you haven't already, have a read on dependency injection. The point is that when a class needs to access the database, it should not have to bother constructing the \PDO object, it should be constructed with it:
class Mapper {
private $pdo;
public function __construct(\PDO $pdo) {
$this->pdo = $pdo;
}
public function createFromId($id) {
$stmt = $this->pdo->prepare("SELECT name FROM foo WHERE id=:id");
$stmt->execute([
":id" => $id,
]);
return $stmt->fetchObject();
}
}
Notice that I directly pass the \PDO object, not a wrapper class. That way, I always have access to all of its capabilities, not only a subset of user-defined functions.
Dependency Injection Container
A dependency injection container helps build your classes, giving them the objects they need, and giving you great flexibility on how to actually build those objects. Here I'll only focus on configuring and sharing a \PDO object through the use of Auryn.
I assume you have installed the required Auryn class, the easier way is using composer. This is out of the scope of this answer, there are multiple resources on how to use it.
Create the injector
$injector = new \Auryn\Injector();
Define the \PDO class parameters
$injector->define("PDO", [
":dsn" => "mysql:host=localhost;charset=utf8;dbname=dbname",
":username" => "user",
":passwd" => "passwd",
":options" => [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
],
]);
You can either write the configuration parameters directly here, or get them from a config file. I like having a config.ini file and using parse_ini_file() to get my configuration options, as I can easily switch databases by editing a config file.
Share the \PDO object
$injector->share("PDO");
This part is really important. This lines makes the injector give the same \PDO object every time it is tasked with constructing a class that needs a connection. Note that the order of the lines is not important, you can share the class before defining it, only make sure to create you database needing classes after writing both those lines.
Create your objects
$mapper = $injector->make("Mapper");
That's it. The injector will create your mapper object, creating the \PDO object if it haven't already been, passing the existing instance if it has.
Autoloading
Assuming you have used composer, you can make use of its great autoloader. Otherwise you can also roll you own autoloader.
The point here is to stop having require() everywhere in your code, especially if you have complex class hierarchies, which you should have in a single responsibility compliant class system.
Wrapping up
With this set up, you now can use the \PDO object in your classes while being assured there will only be one instance per request, without the need to require files everywhere, and without using a singleton anti-pattern.
$database = new Connection();
class Connection
{
function __construct()
{
switch($_SERVER['DOCUMENT_ROOT'])
{
case 'path':
$this->host = 'hostname';
$this->user = 'username';
$this->passwd = 'password';
$this->database = 'dbname';
break;
default :
$this->host = 'localhost';
$this->user = 'root';
$this->passwd = 'root';
$this->database = 'dbname';
break;
}
$this->clink = #mysql_connect($this->host,$this->user,$this->passwd);
#mysql_select_db($this->database,$this->clink);
}
}
$objConn = new mysqlconnect();
$Conn = $objConn->setobjConnect("localhost","root","P#ssw0rd");
class Database{
var $last_query; //Saved result of the last query made
var $last_result; //Results of the last query made
var $func_call; //A textual description of the last query/get_row/get_var call
var $link; //database link
var $lastquery; //last query
var $result; //query result
// Connect to MySQL database
function database() {
$this->link=mysql_connect(DB_HOST, DB_USER, DB_PASS) or die('Server connexion not possible.');
//Set All Charsets to UTF8
mysql_query("SET character_set_results=utf8 , character_set_client=utf8 , character_set_connection=utf8 , character_set_database=utf8 , character_set_server=utf8");
mysql_select_db(DB_NAME) or die('Database connection not possible.');
}
/** Query the database.
* #param $query The query.
* #return The result of the query into $lastquery, to use with fetchNextObject().
*/
function query( $query ){
$this->lastquery=$query;
$this->result=#mysql_query( $query, $this->link );
return $this->result;
}
/** Do the same as query() but do not return nor store result.
* Should be used for INSERT, UPDATE, DELETE...
* #param $query The query.
* #param $debug If true, it output the query and the resulting table.
*/
function execute($query)
{
#mysql_query($query);
}
/** Convenient method for mysql_fetch_object().
* #param $result The ressource returned by query().
* #return An ARRAY representing a data row.
*/
function fetchArray($result){
if ($result == NULL)
$result = $this->result;
if ($result == NULL || mysql_num_rows($result) < 1)
return NULL;
else
return mysql_fetch_assoc($result);
}
/** Close the connecion with the database server.
* It's usually unneeded since PHP do it automatically at script end.
*/
function close()
{
mysql_close($this->link);
}
/** Get the number of rows of a query.
* #param $result The ressource returned by query(). If NULL, the last result returned by query() will be used.
* #return The number of rows of the query (0 or more).
*/
function numRows($result = NULL)
{
if ($result == NULL)
return #mysql_num_rows($this->result);
else
return mysql_num_rows($result);
}
}
![<?php
class mysqlconnect{
private $server=null;private $user=null;private $password=null;private $objConnect=null;
function setobjConnect($s,$u,$p){
$this->server = $s;
$this->user = $u;
$this->password = $p;
return $this->objConnect = mysql_connect($this->server,$this->user,$this->password);
}
}
$objConn = new mysqlconnect();
$Conn = $objConn->setobjConnect("localhost","root","P#ssw0rd");
if($Conn)
{
echo "Database Connect";
}
else
{
echo "Database Connect Failed.";
}
mysql_close($Conn);
?>]
1
Related
I think I've a problem in understanding how OOP works. I already changed the code that it works, but it isn't the propper way I think. Following scenario (No, I'm not creating a userlogin by myself, its really just for local dev. to understand OOP better):
I've a database.php file:
class Database {
/* Properties */
private $conn;
private $dsn = 'mysql:dbname=test;host=127.0.0.1';
private $user = 'root';
private $password = '';
/* Creates database connection */
public function __construct() {
try {
$this->conn = new PDO($this->dsn, $this->user, $this->password);
} catch (PDOException $e) {
print "Error!: " . $e->getMessage() . "";
die();
}
return $this->conn;
}
}
So in this class I'm creating a database connection and I return the connection (object?)
Then I have a second class, the famous User class (actually I'm not using autoload, but I know about it):
include "database.php";
class User {
/* Properties */
private $conn;
/* Get database access */
public function __construct() {
$this->conn = new Database();
}
/* Login a user */
public function login() {
$stmt = $this->conn->prepare("SELECT username, usermail FROM user");
if($stmt->execute()) {
while($rows = $stmt->fetch()) {
$fetch[] = $rows;
}
return $fetch;
}
else {
return false;
}
}
}
So thatare my two classes. Nothing big, as you see. Now, don't get confued about the function name login - Actually I just try to select some usernames and usermails from database and displaying them. I try to achieve this by:
$user = new User();
$list = $user->login();
foreach($list as $test) {
echo $test["username"];
}
And here comes the problem. When I execute this code, I get the following error message:
Uncaught Error: Call to undefined method Database::prepare()
And I'm not sure that I really understand what causes this error.
The code works well when I change the following things:
Change $conn in database.php to public instead of private (I think thats bad...? But when its private, I can only execute querys inside of the Database class, I'm right? So should I put all these querys in the Database class? I think that's bad, because in a big project it will get become really big..)
And the second change I've to do is:
Change $this->conn->prepare to $this->conn->conn->prepare in the user.php file. And here I've really no Idea why.
I mean, in the constructor of the user.php I've a $this->conn = new Database() and since new Database will return me the connection object from DB class, I really don't know why there have to be a second conn->
Do not create classes such as your Database class as it's rather useless. It would make sense to create a database wrapper if it adds some extra functionality to PDO. But given its current code, better to use vanilla PDO instead.
Create a single $db instance from either vanilla PDO or your database class.
Pass it as a constructor parameter into every class that needs a database connection
database.php:
<?php
$host = '127.0.0.1';
$db = 'test';
$user = 'root';
$pass = '';
$charset = 'utf8';
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$opt = [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
\PDO::ATTR_EMULATE_PREPARES => false,
];
$pdo = new \PDO($dsn, $user, $pass, $opt);
user.php
<?php
class User {
/* Properties */
private $conn;
/* Get database access */
public function __construct(\PDO $pdo) {
$this->conn = $pdo;
}
/* List all users */
public function getUsers() {
return $this->conn->query("SELECT username, usermail FROM user")->fetchAll();
}
}
app.php
include 'database.php';
$user = new User($pdo);
$list = $user->getUsers();
foreach($list as $test) {
echo $test["username"],"\n";
}
output:
username_foo
username_bar
username_baz
Check out my (The only proper) PDO tutorial for more PDO details.
This is in reference to a CentOS 7 server running PHP 5.4 and MariaDB 5.5.
I am somewhat new to OOP in PHP. In converting a bunch of scripts from MySQL to MySQLi and from procedural database functions to OOP, I set up this basic Database class:
class Database
{
private $host = "localhost";
private $username;
private $password;
private $database;
private $dbconnect;
function __construct()
{
// Load config file for database connection info
$ini = parse_ini_file("config.ini");
$this->username = $ini['db.user'];
$this->password = $ini['db.pword'];
$this->database = $ini['db'];
}
public function connect()
{
// Only make a new connection if one not already established.
if (empty($this->dbconnect)) {
$mysql = new mysqli($this->host, $this->username, $this->password, $this->database);
if ($mysql->connect_errno) {
throw new appError($mysql->connect_error);
}
$this->dbconnect = $mysql;
}
return $this->dbconnect;
}
public function query($query)
{
$db = $this->connect();
$result = $db->query($query);
if ($db->errno) return false;
return $result;
}
public function select($query)
{
$rows = array();
$result = $this->query($query);
if ($result === false) return false;
// Create array with results
while ($row = $result->fetch_assoc()) {
$rows[] = $row;
}
return $rows;
}
}
With the former procedural database functions that used mysql_*, a persistent database connection was made near the start of the script and the resource ID was stored in a global variable, then all queries were run through that one database connection by accessing the global resource variable. Importantly, this worked well from within other functions and objects. Here's how that function worked:
function db_connect() {
if (!empty($GLOBALS['DBCONNECT']) && is_resource($GLOBALS['DBCONNECT'])) {
return $GLOBALS['DBCONNECT'];
} else {
$result = mysql_connect("localhost", DB_USER, DB_PASSWORD);
$GLOBALS['DBCONNECT'] = $result;
return $result;
}
}
So at the start of each script I'd do this...
db_connect();
And then run my queries like this...
$result = mysql_query($query, db_connect());
This made sure one database connection was made and that all queries are run through that connection.
With the above new Database class, I instantiate it at the start of my script...
$db = new Database;
$db->connect();
But I don't understand how to make that Database object accessible to other objects that need to perform database queries so that the same database connection is used by the entire script. What I do now is essentially this...
class MyClass
{
public function myFunction()
{
$db = new Database;
$data = $db->select("SELECT * FROM mydata WHERE id = 888");
...
}
}
This instantiates a new Database object within the above class, which is creating a new and additional connection to the database, because it can't access the Database $db object created in the parent calling script (that I know of).
Is there a way to use an object to open a persistent MySLQi database connection that all functions and objects loaded by that script can use? Or is this just something that is better done with a procedural function rather than a class and object? Does the solution lie in making the Database properties and its methods static? If I do that, I'm not sure how I load the database username and password from the config.ini file which the connect() function needs and is only done upon instantiation of the Database object.
I guess the basic question here is how do I access the properties or methods in an instantiated object from another object? Or maybe that isn't the question and I'm completely missing something else. Thanks!
You can do something like that a static method with a only a single tone
<?php
/*
* Mysql database class - only one connection alowed
*/
class Database {
private $_connection;
private static $_instance; //The single instance
private $_host = "HOSTt";
private $_username = "USERNAME";
private $_password = "PASSWORd";
private $_database = "DATABASE";
/*
Get an instance of the Database
#return Instance
*/
public static function getInstance() {
if(!self::$_instance) { // If no instance then make one
self::$_instance = new self();
}
return self::$_instance;
}
// Constructor
private function __construct() {
$this->_connection = new mysqli($this->_host, $this->_username,
$this->_password, $this->_database);
// Error handling
if(mysqli_connect_error()) {
trigger_error("Failed to conencto to MySQL: " . mysql_connect_error(),
E_USER_ERROR);
}
}
// Magic method clone is empty to prevent duplication of connection
private function __clone() { }
// Get mysqli connection
public function getConnection() {
return $this->_connection;
}
}
and to make a connection to the database and make a query simple use the lines:
$db = Database::getInstance();
$mysqli = $db->getConnection();
$sql_query = "SELECT foo FROM .....";
$result = $mysqli->query($sql_query);
There are a few options, one simple one (and likely the preferred) would be to use a static factory method in your database class that returns a previously initialized instance of the database class.
class Database
{
//static instance of the class
private static $instance;
public static function getDB()
{
//if the instance is not set, set it
if(is_null(self::$instance))
{
self::$instance = new Database();
self::$instance->connect();
}
return self::$instance;
}
//rest of your class below
}
This static method can be called from anywhere in any context and would allow for getting the same connected instance of the database. Just call $db = Database::getDB() anywhere you need it. For example:
function TestFunction()
{
//get an instance to the database
$dbInstance = Database::getDB();
//then use the database object like you normally would.
$dbInstance->query("SELECT * FROM `someTable`");
}
class TestClass
{
public function doSomething()
{
//get an instance to the database
$dbInstance = Database::getDB();
//then use the database object like you normally would.
$dbInstance->query("SELECT * FROM `someTable`");
}
}
$tc = new TestClass();
$tc->doSomething();
Another option, and one that I use for simplicity, is declare your connection static and reference that everywhere in your database class for queries and such. This allows for creating $db = new Database() anywhere and will just use the same connection. I've had this frowned upon as if it were same as a global because you can really only connect to one database (subsequent connections would overwrite the connection variable) But it worked for me and I didn't need multiple connections. I also like it that I can have each instance remember whatever queries were run on that instance and have a private count of the queries run.
I'm new to classes and PDO as well. I'm trying to write a class with separate functions for connecting to the db and closing the connection so that, in a page, I can:
Open the connection with the $db->connOpen
Perform all the queries I need inside the page
Close the connection at the end of the script with $db->connClose
class database
{
private $host = '';
private $db_name = '';
private $charset = '';
private $username = '';
private $password = '';
public function setHost($host) {
$this->host = $host;
}
public function setDbName($db_name) {
$this->db_name = $db_name;
}
public function setUser($username, $password) {
$this->username = $username;
$this->password = $password;
}
public function connOpen() {
try {
$dsn = "mysql:host=$this->host;dbname=$this->db_name;charset=$this->charset";
$db = new PDO($dsn, $this->username, $this->password, array(PDO::ATTR_PERSISTENT => true));
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
echo $e->getMessage();
}
}
public function connClose() {
$db = null;
}
}
I guess the problem is regarding more the classes syntax than the PDO, since to fire a query like the one below into the page I would need to instantiate again the class PDO, doubling the connection to the db.
$stmt = $dbo->prepare('SELECT * FROM products WHERE id=:id');
$stmt->execute(array(':id' => $id));
$res = $stmt->fetchAll(PDO::FETCH_ASSOC);
My questions are:
How can achieve what I want using a class like the one above?
Is it correct, in this case, using a persistent connection?
Thanks
For some reason, most people who wants to learn OOP starts with a database wrapper that implements singleton and interferes with error handling.
My tip is that you treat the database connection as any other variable:
class Post {
function getByUserId(PDO $connection, $user_id) {
}
}
$database = new PDO(...);
$post->getByUserId($database, 123);
This is called dependency injection.
And don't waste time and energy writing code to avoid useful features, such as:
Centralised error handling
Being able to connect to more than one database
Only care about writing a database wrapper when you really have real features to add on top of PDO. I can think of:
Convert dates to DateTime objects automatically
Pass all queries to a logger
Throw custom exceptions on interesting events, such as duplicate index violation
... and even then be extremely careful to not make PDO worse ;-)
I am using this with one singlton class which give me db ojbect and then I am using that object to query in other classes;
<?php
class db{
/*** Declare instance ***/
private static $instance = NULL;
/**
*
* the constructor is set to private so
* so nobody can create a new instance using new
*
*/
private function __construct() {
/*** maybe set the db name here later ***/
}
/**
*
* Return DB instance or create intitial connection
*
* #return object (PDO)
*
* #access public
*
*/
public static function getInstance() {
if (!self::$instance)
{
self::$instance = new \PDO("mysql:host=".databaseHost.";dbname=".databaseName."", databaseUserName,databasePassword);;
self::$instance-> setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
self::$instance-> setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
}
return self::$instance;
}
/**
*
* Like the constructor, we make __clone private
* so nobody can clone the instance
*
*/
private function __clone(){
}
} /*** end of class ***/
?>
class where i am querying using db object
<?php
class posts {
public function getPostOfUserId($userId,$offset=0,$limit=NULL){
$helperString=" ";
if(!empty($limit))
$helperString=" LIMIT $offset, $limit ";
$executor= db::getInstance()->prepare("SELECT posts.*,users.facebook_id, users.first_name as post_by FROM posts JOIN tagged_friends ON posts.id = tagged_friends.post_id JOIN users ON posts.user_id = users.id WHERE tagged_friends.user_id = :user_id ORDER BY posts.id DESC ".$helperString, array(\PDO::ATTR_CURSOR => \PDO::CURSOR_FWDONLY));
$executor->execute(array(':user_id'=>$userId));
$posts=$executor->fetchAll(\PDO::FETCH_ASSOC);
if(!empty($posts))
return $posts;
else
return ;
}
}
I have one quick question. I have that class written in mysql extension my question is how can I migrate that class from mysql to mysqli object oriented style not procedural.
I'm new at this mysqli extension.
<?php
class DBController {
private $host = "";
private $user = "";
private $password = "";
private $database = "";
function __construct() {
$conn = $this->connectDB();
if(!empty($conn)) {
$this->selectDB($conn);
}
}
function connectDB() {
$conn = mysql_connect($this->host,$this->user,$this->password);
return $conn;
}
function selectDB($conn) {
mysql_select_db($this->database,$conn);
}
function runQuery($query) {
$result = mysql_query($query);
while($row=mysql_fetch_assoc($result)) {
$resultset[] = $row;
}
if(!empty($resultset))
return $resultset;
}
function numRows($query) {
$result = mysql_query($query);
$rowcount = mysql_num_rows($result);
return $rowcount;
}
}
?>
Having such class is a really good idea, but unfortunately moving from mysql_* API to mysqli class is not so simple. It requires a large code rewrite and change in thinking.
If you are planning the upgrade from mysql_* API it probably means you were using PHP 4 until now. If you were using PHP 5, you were using PHP 4 way of thinking. PHP has come a long way since then. We now have proper classes, namespacing, better error reporting, etc. When it comes to database interactions we now have two new extensions: mysqli and PDO. Out of these two you should be using PDO. However, if you are using a wrapper class like the one you are trying to create changing from mysqli to PDO should be very easy. For this reason alone such a wrapper class is a good idea.
The primary reason for mysqli extension was to keep a familiar syntax of the old extension and add necessary new features such as prepared statements and proper error reporting. If you were used to putting PHP variables directly into SQL queries, you must change your way of thinking. You should now use parameterized queries and bind the values separately.
A simple example of what your improved class could look like is this:
<?php
class DBController {
/**
* mysqli instance
*
* #var \mysqli
*/
private $mysqli;
public function __construct(
$host = null,
$username = null,
$passwd = null,
$dbname = null,
$charset = 'utf8mb4',
$port = null,
$socket = null
) {
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$this->mysqli = new \mysqli($host, $username, $passwd, $dbname, $port, $socket);
$this->mysqli->set_charset($charset);
}
public function runQuery(string $sql, array $params = []): ?array {
$stmt = $this->mysqli->prepare($sql);
if ($params) {
$stmt->bind_param(str_repeat("s", count($params)), ...$params);
}
$stmt->execute();
if ($result = $stmt->get_result()) {
return $result->fetch_all(MYSQLI_BOTH);
}
return null;
}
}
Warning: This is not a full implementation of such wrapper class. You most likely need to implement other methods and add more logic as per your needs. This is only for demonstration purposes.
First, in the construct method, we should execute 3 steps. Enable error reporting, create an instance of the mysqli class and set the correct charset (use utf8mb4 which was added in MySQL 5.5 and is now the standard charset).
Your runQuery() method now accepts 2 arguments. The first one is your SQL query with placeholders instead of interpolated values, and the second one is an array of the values to be bound.
Most likely you do not need selectDB() and you definitely do not need numRows(). If you want to know the number of rows in your retrieved array, you can count them in PHP using count().
Using such class is very simple.
$db = new DBController('localhost', 'username', 'password', 'db_name');
$result = $db->runQuery('SELECT Id, Name FROM table1 WHERE uuid=?', ['myuuid']);
if ($result) {
// Get the name of found record
echo $result[0]['Name'];
} else {
echo 'No records found';
}
If you wanted to switch the implementation to PDO, you can replace the class without changing the way you use it.
class DBController {
/**
* PDO instance
*
* #var \PDO
*/
private $pdo;
public function __construct(
$host = null,
$username = null,
$passwd = null,
$dbname = null,
$charset = 'utf8mb4',
$port = null,
$socket = null
) {
$dsn = "mysql:host=$host;dbname=$dbname;charset=$charset;port=$port;socket=$socket";
$options = [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_EMULATE_PREPARES => false,
];
$this->pdo = new PDO($dsn, $username, $passwd, $options);
}
public function runQuery(string $sql, array $params = []): ?array {
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll();
}
}
Warning: This is not a full implementation of such wrapper class. You most likely need to implement other methods and add more logic as per your needs. This is only for demonstration purposes.
I don't want to create a discussion about singleton better than static or better than global, etc. I read dozens of questions about similar subjects on SO, but I couldn't come up with an answer to this SPECIFIC question, so I hope someone could now illuminate me by answering this question with one (or more) real simple EXAMPLES, and not just theoretical discussions.
In my app I have the typical DB class to abstract the DB layer and to perform tasks on DB without having to write everywhere in code mysql_connect / mysql_select_db / mysql...
I could write the class either as a STATIC CLASS:
class DB
{
private static $connection = FALSE; //connection to be opened
//DB connection values
private static $server = NULL; private static $usr = NULL; private static $psw = NULL; private static $name = NULL;
public static function init($db_server, $db_usr, $db_psw, $db_name)
{
//simply stores connections values, without opening connection
}
public static function query($query_string)
{
//performs query over alerady opened connection, if not open, it opens connection 1st
}
...
}
OR as a SINGLETON:
class DBSingleton
{
private $inst = NULL;
private $connection = FALSE; //connection to be opened
//DB connection values
private $server = NULL; private $usr = NULL; private $psw = NULL; private $name = NULL;
public static function getInstance($db_server, $db_usr, $db_psw, $db_name)
{
//simply stores connections values, without opening connection
if($inst === NULL)
$this->inst = new DBSingleton();
return $this->inst;
}
private __construct()...
public function query($query_string)
{
//performs query over already opened connection, if connection is not open, it opens connection 1st
}
...
}
Then after in my app if I want to query the DB i could do
//Performing query using static DB object
DB:init(HOST, USR, PSW, DB_NAME);
DB::query("SELECT...");
//Performing query using DB singleton
$temp = DBSingleton::getInstance(HOST, USR, PSW, DB_NAME);
$temp->query("SELECT...");
To me Singleton has got the only advantage to avoid declaring as static each method of the class. I'm sure some of you could give me an EXAMPLE of real advantage of singleton in this specific case. Thanks in advance.
What is wrong with the following (simplified) example:
class Database
{
protected $_connection;
protected $_config;
public function __construct( array $config ) // or other means of passing config vars
{
$this->_config = $config;
}
public function query( $query )
{
// use lazy loading getter
return $this->_getConnection()->query( $query );
}
protected function _getConnection()
{
// lazy load connection
if( $this->_connection === null )
{
$dsn = /* create valid dsn string from $this->_config */;
try
{
$this->_connection = new PDO( $dsn, $this->_config[ 'username' ], $this->_config[ 'password' ] );
}
catch( PDOException $e )
{
/* handle failed connecting */
}
}
return $this->_connection;
}
}
$db1 = new Database( array(
'driver' => 'mysql',
'host' => 'localhost',
'dbname' => 'test',
'username' => 'test_root',
'password' => '**********'
) );
$db2 = new Database( array(
'driver' => 'pgsql',
'host' => '213.222.1.43',
'dbname' => 'otherdb',
'username' => 'otherdb_root',
'password' => '**********'
) );
$someModel = new SomeModel( $db1 );
$someOtherModel = new SomeOtherModel( $db2 );
$yetAnotherModel = new YetAnotherModel( $db2 );
This demonstrates how you can make use of lazy loading connections, and still have flexibility to use different database connections.
The database instances will only connect to their individual connection when an object that consumes one of the instances (in this case one of the models) decides to call a method of the instance.
In my most recent project, I actually went against the "good" design principles by making the database class entirely static. The reason behind this is that I used a lot of caching on PHP objects. Originally I had the database passed in through the constructor of each object as a dependency injection, however I wanted to make sure that the database didn't have to connect unless absolutely necessary. Thus, using a database as a member variable of that object would not have been practical because if you unserialized an object from the cache, you wouldn't want to connect to the database unless you actually performed an operation on it.
So in the end I had only two (public) static functions, Database::fetch() and Database::execute() which would check whether or not it had already connected, and if not, it would connect and perform the query. This way I wouldn't have to worry about deserialization and would connect as seldom as possible. It technically makes unit testing impossible though.
You don't always have to follow every single good practice. But I would still recommend against doing what I did since some would consider it premature optimization.
My advice: STOP using Singleton and static all together.
Why? Because you will insert dependencies that will render your code unusable in other projects, and will not allow to unit test it. Also forget about loose coupling if using singleton.
The alternatives? Dependency Injection.
http://www.potstuck.com/2009/01/08/php-dependency-injection
Making DB library static is certainly shorter and quicker, than doing:
$db = DBSingleton::blabla(); // everytime I need ya
But also, since it is global, tempting to use everywhere.
So, choose other methods if you want clean code... and choose static if you need quick code ;-)
/* Data base*/
class Database
{
/* Database field definition */
private static $_instance; /instance
private $_connection;
private $DB_USER = "database_user_name_here";
private $DB_PASS = "your_password_here";
private $DB_NAME = "your_database_name_here";
private $DB_SERVER = "localhost";
/* Initiate the database connection */
private function __construct()
{
$this->_connection = new mysqli($this->DB_SERVER ,
$this->DB_USER ,
$this->DB_PASS ,
$this->DB_NAME);
/* Test if connection succeeded */
if (mysqli_connect_errno()) {
die("Database connection failed: " .
mysqli_connect_error() .
" (" . mysqli_connect_errno() . ")"
);
}
}
/**
* Instance of the database
* #return Database
*
*/
public static function Instance()
{
if (!self::$_instance) { // If no instance then make one
self::$_instance = new self();
}
return self::$_instance;
}
/**
* Void duplicate connection
*/
private function __clone() { }
/* Return a connection */
public function getConnection()
{
return $this->_connection;
}
}
/** This is how you would use it in a different class.
#var TYPE_NAME $connection */
$db = Database::Instance();
$connection = $db->getConnection();