Accessing arrays from within a class - php

I have a config file that contains an array of settings/parameters. The problem I'm running into is accessing this array from within a class. My first guess was to do something like this.
Settings File:
$config = array (
'db' => array (
'host' => 'localhost',
'password' => 'password',
'port' => 3306,
'database' => 'my_db',
'user' => 'root'
),
'email' => array (
'username' => 'someone#somewhere.com',
'smtp' => 'smtp.somewhere.com',
'ssl' => true,
'password' => 'my_password',
'port' => 587
)
// other stuff
);
The Confiuration class
class CoreConfig
{
static $confArray;
public static function read($name)
{
return self::$confArray[$name];
}
public static function write($name, $value)
{
self::$confArray[$name] = $value;
}
}
And use it like: CoreConfig::write('username', $config['email']['username']);. Example using it in a class:
Usage
class SomeClass {
public function __construct() {}
public function getUserName() {
return CoreConfig::read('username');
}
}
Although this works, it feels like it's considered bad practice. Alternatively I can take the initial $config and load it right away in my $confArray instead "writing", which looks like an ever worse idea. Is there another approach to this?

It sure feels a bit weird to hard code anything in an application, even if you're doing a configuration file. There are many methods for doing what you're trying to do, but the most important thing is to keep all "hard coded" values at the same place.
If you have access to any kind of SQL to do your work, I would recommend to put all configurations values in a data table. Doing this, you avoid having any hard coded values directly in the code, since they're in a database.
Config class
class CoreConfig
{
static $confArray = null;
public static function read($name)
{
if(self::$confArray == null)
self::loadConfig();
return self::$confArray[$name];
}
private static function loadConfig()
{
self::$confArray = DatabaseManager::fetchAll("SELECT * FROM config");
}
}
Database Manager class
class DatabaseManager
{
private static $instance = null;
private static $host = "YOUR HOST NAME HERE";
private static $dbName = "YOUR DATABASE NAME";
private static $dbUser = "YOUR DATABASE USER";
private static $dbPass = "YOUR DATABASE PASSWORD";
private function __construct() {}
private static function getDb()
{
if(is_null(self::$instance))
{
self::$instance = new PDO("mysql:host=" .self::$host. ";dbname=" .self::$dbName, self::$dbUser, self::$dbPass);
self::$instance->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
return self::$instance;
}
public static function fetchAll($SQL)
{
$return = null;
try
{
$return = self::getDb()->query($SQL)->fetchAll();
}
catch(Exception $e)
{
// If you want to display errors directly on the page
echo "SQL Query Error<br />";
echo "Request: $SQL<br />";
echo $e->getMessage();
}
return $return;
}
}
You may refer to PDO Documentation on php.net in order to add features to the Database manager. You will have to add more methods if you want to be able to write in the database using PHP.
If you have no database options, then you can use an XML or JSON configuration file and parse it into an array at the moment of reading its values.
Hope this helps!

Related

PHP Warning Deprecated: Creation of dynamic property is deprecated

Deprecated: Creation of dynamic property ... is deprecated
I'm seeing this more and more, and I'm not sure what I need to do to stop this warning.
This is my class:
class database {
public $username = "root";
public $password = "pasword";
public $port = 3306;
public function __construct($params = array())
{
foreach ($params as $key => $value)
{
$this->{$key} = $value;
}
}
}
This is how I'm instanacing it.
$db = new database(array(
'database' => 'db_name',
'server' => 'database.internal',
));
Which gives me two messages:
Deprecated: Creation of dynamic property database::$database is deprecated
Deprecated: Creation of dynamic property database::$server is deprecated
The warning is telling you that there is a property you are trying to set which isn't listed at the top of the class.
When you run this:
class database {
public $username = "root";
public $password = "pasword";
public $port = 3306;
public function __construct($params = array())
{
foreach ($params as $key => $value)
{
$this->{$key} = $value;
}
}
}
$db = new database(array(
'database' => 'db_name',
'server' => 'database.internal',
));
It is roughly equivalent to this:
class database {
public $username = "root";
public $password = "pasword";
public $port = 3306;
}
$db = new database;
$db->database = 'db_name';
$db->server = 'database.internal';
The warning is that there is no line in the class definition saying that $db->database or $db->server exist.
For now, they will be dynamically created as untyped public properties, but in future, you will need to declare them explicitly:
class database {
public $database;
public $server;
public $username = "root";
public $password = "pasword";
public $port = 3306;
public function __construct($params = array())
{
foreach ($params as $key => $value)
{
$this->{$key} = $value;
}
}
}
$db = new database(array(
'database' => 'db_name',
'server' => 'database.internal',
));
In some rare situations, you actually want to say "the properties of this class are whatever I decide to add at run-time"; in that case, you can use the #[AllowDynamicProperties] attribute, like this:
#[AllowDynamicProperties]
class objectWithWhateverPropertiesIWant {
public function __construct($params = array())
{
foreach ($params as $key => $value)
{
$this->{$key} = $value;
}
}
}
So the warnings are coming from the constructor adding dynamic class properties. If you don't have to pass in those fields dynamically and really, it does seem like you're overcomplicating something simple then try it like this.
class database {
public $username = "root";
public $password = "pasword";
public $port = 3306;
public $database = 'db_name';
public $server = 'database.internal';
}
$db = new database();
Is there a reason you needed dynamic parameters? You could also do this:
class database {
public $username = "root";
public $password = "pasword";
public $port = 3306;
public $database;
public $server;
public function __construct($params = array())
{
foreach ($params as $key => $value)
{
$this->{$key} = $value;
}
}
}
If you add the parameters ahead of time, they're not dynamic, and you're just assigning a value to something already existing.
This should work now without any warnings.
$db = new database(array(
'database' => 'db_name',
'server' => 'database.internal',
));
You can avoid the need to specify an explicit list of member variables by storing them in a single array, like this:
class database {
public $data;
public function __construct($params = array())
{
foreach ($params as $key => $value)
{
$this->data[$key] = $value;
}
}
}
Then you can instantiate a database object with any params you want:
$db = new database(array(
'database' => 'db_name',
'server' => 'database.internal',
'foo' => 'bar', // etc.
));
Of course, you'll probably want to document what the expected params are, but at least you'll be able to pass in anything you want, and be able to use it in the future, without changing the class definition.
The warning message you are seeing is related to the use of a feature in PHP called "dynamic properties". Dynamic properties allow you to set and get object properties by using variable names, like you are doing in the __construct method of your database class.
Quick fix:
public function __construct(array $params)
{
$this->username = $params['username'] ?? null;
$this->password = $params['password'] ?? null;
$this->port = $params['port'] ?? null;
}
To fix this warning, you can remove the use of dynamic properties in your code and use a more modern way of setting object properties.
Named arguments:
class database
{
public function __construct(
public string $username,
public string $password,
public int $port,
) {}
}
$db = new database(
username: 'root',
password: 'password',
port: 3306,
);
Alternatively, you can also use the __set and __get magic methods to set and get object properties, like this:
public function __set($key, $value)
{
$this->$key = $value;
}
public function __get($key)
{
return $this->$key;
}
This will allow you to use the $object->property notation to set and get object properties, without triggering the warning message.
An alternative solution, which is specific to this example in that the parameters are quite easy to identify. Databases have common parameters that are referenced.
Using PHP 8 named parameters and Constructor property promotion, allows you to specify all of the possible parameters in the constructor (which is a bit long winded, but great for code completion) and then when creating an instance, the parameter names are fixed...
class database
{
public function __construct(
private string $database = '',
private string $server = '',
private string $port = '',
private string $user = '',
private string $password = ''
) {
}
}
Then call with
$db = new database(
database: 'db_name',
server: 'database.internal',
);
Adding this just above the class{ will fix it:
#[\AllowDynamicProperties]
simply replace this:
class database {
for this:
class database extends \stdClass {

PHP PDO close mysql connection in DatabaseFactory file

do you guys have an idea why I can't close the mysql connection?
Here is my code:
class DatabaseFactory {
private static $factory;
private $connection = array(
"DB_HOST" => "localhost",
"DB_PASS" => "*************",
"DB_USER" => "*************",
"DB_PORT" => "3306",
"DB_CHARSET" => "utf8"
);
public static
function getFactory() {
if(!self::$factory) {
self::$factory = new DatabaseFactory();
}
return self::$factory;
}
private $db;
public
function getconnection($name) {
echo "opened";
try {
$options = array(
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_OBJ,
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_WARNING
);
$this->db = new \PDO('mysql:host='.$this->connection["DB_HOST"].';dbname='.$name.';port=' .$this->connection["DB_PORT"].';charset='.$this->connection["DB_CHARSET"], $this->connection["DB_USER"], $this->connection["DB_PASS"], $options);
} catch (PDOException $e) {
exit($e->getMessage());
}
return $this->db;
}
public
function __destruct() {
echo "closed";
try {
$this->db = null;
unset($this->db);
} catch (PDOException $e) {
exit($e->getMessage());
}
}
}
$database = DatabaseFactory::getFactory()->getconnection("Admin");
$query = $database->prepare("SELECT * FROM tester WHERE t_u_id = :u_id");
$query->execute(array(":u_id" => "281123341528-D050C4-91A0BA-1CB0C8-8112334152855AC373"));
The connection is connecting but it is not closed.
The method __destruct will be called but the mysql total connections count doesn't decrease.
Why don't you implement and call a method (manually instead of relying on __destruct()) like Fil does in his response here?
Add the following static method to your DatabaseFactory class
static function closeConnection(&$conn) {
$conn=null;
}
So after your connections, you can NULL your connection reference (which is akin to calling $mysqli_conn->close();) later in your code like this:
$database = DatabaseFactory::getFactory()->getconnection("Admin");
$query = $database->prepare("SELECT * FROM tester WHERE t_u_id = :u_id");
$query->execute(array(":u_id" => "281123341528-D050C4-91A0BA-1CB0C8-8112334152855AC373"));
DatabaseFactory::closeConnection($database)
EDIT:
Also, I wanted to mention that by relying on __destruct(), you are subject to the following as per the PHP docs:
PHP 5 introduces a destructor concept similar to that of other
object-oriented languages, such as C++. The destructor method will be
called as soon as there are no other references to a particular
object, or in any order during the shutdown sequence.
In regards to your question, the method __destruct() might not be called because the static class var private static $factory is never being nulled. Therefore, the class always has a reference to "itself" preventing __destruct() from being called.
I hope this helps!

Passing PDO object into class - PHP Fatal error: Call to a member function execute() on a non-object

I am refactoring a (procedural) PHP Library I wrote a while back into a lightweight OOP framework. I'm getting caught up with trying to pass a PDO object to be used in a class. Here's what I've got so far.
Config.php
<?php
class Config {
// Database Variables
private $db_type;
private $db_host;
private $db_user;
private $db_pass;
private $db_name;
private $db_path; // for sqlite database path
private $db_char; // charset
// Site Variables
private $s_protocol;
private $s_subdomain;
private $s_domain;
private $s_tld;
private $s_dir;
private $s_name;
private $s_description;
private $s_path;
private $s_visibility;
private $s_pipe;
private $s_apps;
private $s_hooks;
private $s_blocks;
private $s_assets;
// User Default
private $u_groupid;
public function __construct($config) {
$this->set($config);
}
public function set($config) {
if (!empty($config) && is_array($config)) {
foreach ($config as $k => $v) {
if (property_exists(get_class($this), $k)) {
$this->$k = $v;
}
}
return true;
}
else { return false; }
}
public function get($config) {
if (!empty($config)) {
return $this->$config;
}
}
public function domain() {
return $this->get('s_protocol') .'://'. $this->get('s_domain') . $this->get('s_tld') .'/'. $this->get('s_dir');
}
}
?>
Database.php
<?php
class Database extends PDO {
private $config;
public function __construct($config) {
$this->config = $config;
switch($this->config->get('db_type')) {
case 'mysql':
case 'pgsql':
try {
return new PDO(
$this->config->get('db_type') .':dbname='. $this->config->get('db_name') .';host='. $this->config->get('db_host'),
$this->config->get('db_user'),
$this->config->get('db_pass')
);
}
catch(PDOException $e) {
die($e->getMessage());
}
break;
case 'sqlite':
try {
return new PDO($this->config->get('db_type') .':'. $this->config->get('db_path'));
}
catch(PDOException $e) {
die($e->getMessage());
}
break;
case 'firebird':
try {
return new PDO(
$this->config->get('db_type') .':dbname='. $this->config->get('db_host') .':'. $this->config->get('db_path'),
$this->config->get('db_user'),
$this->config->get('db_pass')
);
}
catch(PDOException $e) {
die($e->getMessage());
}
break;
case 'informix':
try {
return new PDO(
$this->config->get('db_type') .':DSN='. $this->config->get('db_name'),
$this->config->get('db_user'),
$this->config->get('db_pass')
);
}
catch(PDOException $e) {
die($e->getMessage());
}
break;
case 'oracle':
try {
return new PDO(
'OCI:dbname='. $this->config->get('db_name') .';charset='. $this->config->get('db_char'),
$this->config->get('db_user'),
$this->config->get('db_pass')
);
}
catch(PDOException $e) {
die($e->getMessage());
}
break;
}
}
}
?>
Auth.php
<?php
class Auth {
// Set Database object
protected $db;
// User fields in users table
private $id;
private $email;
private $password;
private $firstname;
private $lastname;
private $displayname;
private $groupid;
private $ip;
private $created;
private $updated;
private $cookie;
private $sessionid;
private $lastlogin;
private $token;
private $active;
public function __construct($dbh) {
$this->db = $dbh;
}
public function add($params) {
$sql = '
INSERT INTO
`users` (
';
$cols = array_keys($params);
$col_string = implode(', ', $cols);
$sql .= $col_string .'
)
VALUES (
';
array_walk($cols, function(&$v, $k) { $v = ':'. $v; });
$col_string = implode(', ', $cols);
$sql .= $col_string .'
)
';
$stmt = $this->db->prepare($sql);
$stmt->execute($params);
}
public function remove($params) {
}
public function update($params) {
}
public function get($params) {
}
protected function set($params) {
if (!empty($params) && is_array($params)) {
foreach ($params as $k => $v) {
if (property_exists(get_class($this), $k)) {
$this->$k = $v;
}
}
return true;
}
else { return false; }
}
}
?>
init.php
<?php
session_start();
$params = array(
'db_type' => 'mysql',
'db_host' => '127.0.0.1',
'db_user' => 'user',
'db_pass' => 'password',
'db_name' => 'database',
'u_groupid' => 4
);
require_once('Config.php'); $c = new Config($params);
require_once('Database.php'); $db = new Database($c);
require_once('Auth.php'); $u = new Auth($db);
$user = array(
'email' => 'newperson#email.com',
'password' => md5('password'),
'firstname' => 'Jeff',
'lastname' => 'Wilson',
'displayname' => 'Jeff Wilson',
'groupid' => $c->get('u_groupid'),
'ip' => $_SERVER['REMOTE_ADDR'],
'created' => date('Y-m-d H:i:s'),
'sessionid' => session_id(),
'active' => 1,
);
$u->add($user);
?>
PHP Fatal error: Call to a member function execute() on a non-object in Auth.php on line 46
This is line 46:
$stmt->execute($params);
As far as I know I'm passing the PDO object to the Auth class correctly. It shouldn't say it's a non-object. Can anyone else see what's wrong here?
The constructor in the Database class is returning a value (a PDO object). The __construct() function should not explicitly return a value. Since your Database class extends PDO, call the parent constructor instead:
parent::__construct(
$this->config->get('db_type') .':dbname='. $this->config->get('db_name') .';host='. $this->config->get('db_host'),
$this->config->get('db_user'),
$this->config->get('db_pass')
);
Unless the pdo instance is set explicitly to use exceptions for error reporting you have to check the return value of PDO::prepare
$stmt = $this->db->prepare($sql);
if ( !$stmt ) {
// prepare failed
// the array returned by $this->db->errorinfo() most likely contains more info about the error
// don't send it unconditionally,directly to the browser, see https://www.owasp.org/index.php/Top_10_2013-A6-Sensitive_Data_Exposure
}
else {
$result = $stmt->execute($params);
if ( !$result ) {
// not ok
}
else {
// ok
}
}
edit: I can leave out the first part of my answer, since Alex Ivey already wrote it. ;-)
Just image behind the scene php is doing something like
function __internal_newDatabase() {
// just image the parser/compiler creates this function
// from your class defintion
$instance = array(
'properties'=>array('config'=>null),
'methods'=>array('beginTransaction'=>PDO::beginTransaction, 'prepare'=>PDO::prepare, ...)
);
// some magic function that calls a method and "replaces" $this by the second parameter
invoke(Database::__construct, $instance);
return $instance;
}
and when your script contains new Database instead it calls __internal_newDatabase(). That's (ooooversimplified) what happens and therefore you can not just "change" the instance within your constructor "method" by returning a different instance. Your constructor is supposed to make this instance fly (or bail out by throwing an exception).
Your class Database is derived from PDO, i.e. it is supposed to behave as a PDO. In other languages that implies that the/a base class' constructor must be called. PHP doesn't enforce that. But in this case leaves your Database instance unusable. You have to explcitily call the parent's constructor as Alex's answer shows.
But there are other issues with this class. (First a confession: I'm biased as can be against class Database. In virtually all in all cases it's just wrong and therfore automagically raises a red flag for me)
Foremost: Given its name and the way you use it, it's superfluous. It's just a configuration detail not a class derived from PDO. More likely it would be a factory and/or something within an IoC container (if you use that).
On the other hand it might not be just a configuration detail but might (and probably will) cause different implementions for different databases. PDO is not a database abstraction, just a unified access layer.
Your class Auth.php doesn't care about the concrete sql dialect used - and this particular query will most likely work for all database systems supported by PDO. But there will sooner or later be queries that have to be customized for the different RDBMSs. And then your base class would probably be called something like DB_Adapter and there would be MySQL_Adapter extends DB_Adapter and so on....
Add this method to your Database class
public function getConnection() {
return new PDO(
$this->config->get('db_type') .':dbname='. $this->config->get('db_name') .';host='. $this->config->get('db_host'),
$this->config->get('db_user'),
$this->config->get('db_pass')
);
}
call prepare statement like this:
$conn = $this->db->getConnection();
$stmt = $conn->prepare($sql);
Your $conn has to be PDO object in this case
The main problem here is while $db = new Database($c); seems to be fine at the first glance, calling $db->prepare is not good because $db is instance of Database, but has to be instance of PDO
One of the ways to improve a bit connection handling would be:
In your Database class to have a private $conn for connection
class Database extends PDO {
private $config;
private $conn;
public function __construct($config) {
$this->config = $config;
switch($this->config->get('db_type')) {
case 'mysql':
case 'pgsql':
try {
$this->conn = new PDO(
$this->config->get('db_type') . ':dbname=' . $this->config->get('db_name') . ';host=' . $this->config->get('db_host'),
$this->config->get('db_user'),
$this->config->get('db_pass')
);
} catch(PDOException $e) {
die($e->getMessage());
}
break;
// ...
}
}
THEN in the same class new method to return connection:
public function getConnection() {
return $this->conn;
}
AND FINALLY call it:
$this->db->getConnection()->prepare($sql)

How to access variable outside function without globals variables

I have a config.php file with:
$globalConf = array(
'DbConfig' => array(
'Hostname' => 'localhost',
'Username' => 'myuser',
'Password' => 'xxxxxxxx',
'Database' => 'mydb'
)
);
and in other file (this includes the config.php) i have this function:
function db_connect()
{
if (!($db_link = mysqli_connect($globalConf['DbConfig']['Hostname'], $globalConf['DbConfig']['Username'], $globalConf['DbConfig']['Password'], $globalConf['DbConfig']['Database'])))
exit();
return $db_link;
}
This doesn't work. I would need to include the global $globalConf inside the db_connect() function, but I don't want to do this (i have more configurations variables and multiple functions that require those and I don't want to use a global $variable in all functions and subfunctions).
I want to set the $globalConf as a persistent global variable (possibly accesible like class?), for example:
mysqli_connect(myglobal::$globalConf['DbConfig']['Hostname'])
how can do this?
Solved:
class MainConf {
public static $globalConf = array(
'DbConfig' => array(
'Hostname' => 'localhost',
'Username' => 'myuser',
'Password' => 'xxxxxxxx',
'Database' => 'mydb'
)
);
}
and:
function db_connect()
{
if (!($db_link = mysqli_connect(MainConf::$globalConf['DbConfig']['Hostname'], MainConf::$globalConf['DbConfig']['Username'], MainConf::$globalConf['DbConfig']['Password'], MainConf::$globalConf['DbConfig']['Database'])))
exit();
return $db_link;
}
Other than passing the data to the function, which has already been mentioned, you could create a class for your configuration or global data and use a static access method to retrieve them.
<?php
class DbConfig {
private static $data = array(
'Hostname' => 'localhost',
'Username' => 'username',
// ...
);
public static function get($name) {
return array_key_exists($name, self::$data) ? self::$data[$name] : '';
}
}
Then in your your functions you would access it like so
DbConfig::get('Hostname');
Pass data into function using parameters... for example:
function db_connect($dbinfo) {
if (!($db_link = mysqli_connect($dbinfo['Hostname'], $dbinfo['Username'], $dbinfo['Password'], $dbinfo['Database'])))
exit();
return $db_link;
}
and call it like
db_connect($globalConf['DbConfig'])
I think this is the easiest way.
Update
class MainConf {
public $db; // this will get your $globalConf['DbConfig'] array
function __construct($dbinfo) {
$this->db = $dbinfo; // assign it to $db on class construction
}
function db_connect() {
// use it where you want
if (!($db_link = mysqli_connect($this->db['Hostname'], $this->db['Username'], $this->db['Password'], $this->db['Database'])))
exit();
return $db_link;
}
}
Now you can pass your external data into class like this:
$conf = new MainConf($globalConf['DbConfig']);
$dblink = $conf->db_function();
This way you don't have to define your data inside the class. Just pass it as parameter, assign that param to class variable and use these information everywhere you want inside the class.
But I guess you're using these info only for db_connect() so it's not necessary to keep it on global level inside class. If you call that method manually you can add $dbinfo parameter in db_connect() so you don't need __construct() method and your class will look like this
class MainConf {
function db_connect($dbinfo) {
if (!($db_link = mysqli_connect($dbinfo['Hostname'], $dbinfo['Username'], $dbinfo['Password'], $dbinfo['Database'])))
exit();
return $db_link;
}
}
And now call it this way
$conf = new MainConf;
$dblink = $conf->db_connect($globalConf['DbConfig']);
Just choose what the best fits your needs.

PHP - a DB abstraction layer use static class vs singleton object?

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

Categories