I've got a PHP database class which connects to MySQL and wraps up all the PDO code and I use it to query the database. Basically in the page controller I make a new object:
$db = new Database($dbConfig);
Then I can get data from the database like so using a prepared query:
$params = array('username' => $username);
$result = $db->preparedSelect('select password, salt from users where username = :username', $params);
Which copies the PDO statement results into a new assoc array and returns just the database results back to the calling page. I iterate through them with a simple foreach like so:
foreach ($result as $key => $val)
{
$password = $val['password'];
$salt = $val['salt'];
}
Ok so lets say I want another class to use my $db object so it can access the database in some of the methods. At the moment the other class looks like this:
class General
{
// Database object
private $db;
public function __construct($db)
{
$this->db = $db;
}
}
That works well but I'm just wondering if the constructor should look like this:
public function __construct(&$db)
{
$this->db = $db;
}
That should mean I'm passing it in via reference and not copying the object into the other class. I don't want a copy of the $db object inside the class, I want it to use the existing database object so I don't have multiple copies of it floating around using up memory.
Is there any difference in PHP5 between passing it in as $db or &$db? From doing some reading, PHP5 by default passes objects by reference, and other people saying it now does it the Java way and some say using the & makes a hard link whatever that is. I'm confused. What's the best way to do it?
Many thanks!
There is a difference, but it's not really the difference you may think.
In PHP5, "$db" holding an object is basically equivalent to a "Foo *" in C or C++. In other words, $db doesn't store the whole object, it just stores a small token that lets the code find the object when necessary. When you pass this token by value, it's as fast as passing an integer value rather than a copy of the entire object. But if you assign $db, it doesn't change the value in the caller because you're changing your local variable holding the token to contain a different token.
If the function takes "&$db", that's basically the equivalent of passing "Foo **" in C, or more correctly a function taking a "Foo *&" in C++. The call is just as fast since it's the same size thing that's being passed, but inside the function if you assign to $db it will change the value of $db in the caller because the "pass by reference" variable points you to the memory location holding the token in the caller.
The best way to do it is to pass by value (do not use "&") unless you know what you're doing and why you're doing it.
That's a good question.
You can always do a test by opening a $db handle, passing it to a function, and checking them via the === operator to make sure they are the same object.
This would be a good job for static methods. That is how many frameworks accomplish the same task.
class DB
{
private static $db = FALSE:
public static function init($dbConfig)
{
if(! self:$db)
{
self::$db = new Database($dbConfig);
}
}
public static function preparedSelect($sql, $params)
{
if(! self::$db)
{
die("call the init method first");
}
// db stuff, where you would call $this->db call self::$db
}
}
So in your other classes where you want to make calls to the database all you would have to do is:
class General
{
public function __construct()
{
DB::init($dbConfig);
}
public function someMethod()
{
$params = array('username' => $username);
$result = DB::preparedSelect('select password, salt from users where username = :username', $params);
}
}
Related
This question already has an answer here:
How to use PDO connection in other classes?
(1 answer)
Closed 2 years ago.
I have 2 class in my CMS and there are crud functions in "User Class". How can I use database class in another class. I know they both work. But I was previously transferring with the "extends" method. But every time I created an object, the database connection was duplicated. So "extends" was fault for me. I don't want to fault again.
So which is best solution? Do any of these solutions duplicate the database connection?
databaseclass.php:
class database
{
public $connect;
function __construct() {
$this->connect();
}
public function connect() {
$this->connect = new PDO('mysql:host=' . SERVER . ';dbname=' . DB_NAME . ';charset=utf8', DB_USER, DB_PASS);
}
}
userclass.php:
class user
{
function getUser() {
# select an user codes
}
}
Solution - Injection database class
class user
{
function getUser($database) {
$mysql_command = $database->connection->prepare('SELECT * FROM users WHERE id = 1');
$mysql_command->execute();
return $mysql_command->fetch(PDO::FETCH_ASSOC);
}
}
Solution - Global database class (I want that but everybody suggest first solution) (I want because this solution does not require me to inject each time I create a user object) userclass.php:
class user
{
function getUser() {
global $database;
$mysql_command = $database->connection->prepare('SELECT * FROM users WHERE id = 1');
$mysql_command->execute();
return $mysql_command->fetch(PDO::FETCH_ASSOC);
}
}
I added a third option here and gave some personal opinion about the others:
Solution a) Pass as parameter to your methods
This way you always need to pass it inside your functions which makes the API of your class less clean. Imagine we want to get a user by id. This could look like: getUserById($database, 123). This will require everywhere to know how to fetch the databse. I would try to avoid this.
Solution b) Use it as global
I would try avoid global wherever I can. My first argument would be that it makes your class less testable and can change the behaviour dependent on the context. Here is a nice post about it with more examples: https://stackoverflow.com/a/5166527/12880865
Solution c) Use dependency injection with the __constructor.
I would add another way of solving it by using dependency injection at constructor level already. This way you can use getUsers() or getUserById($id) without passing the database all the time. This way your class defines it's own dependencies very clearly for the user of this class.
this solution does not require me to inject each time I create a user object
To avoid this you could use container which could resolves your class dependencies automatically when you try to retrieve them.
Example with your code:
class user
{
protected $database;
public function __construct(database $database) {
$this->database = $database;
}
function all() {
# select an user codes
$this->database->query('....');
}
function getById($id) {
# select an user codes
$this->database->query('....', ['id' => $id]);
}
}
which you can then inject like this:
$database = new Database();
// Here you'll pass your database connection into the repository
$users = new UserRepository($database);
$users->all(); // returns an array of all users
$users->getById(123); // returns a specific user by id
// Here you'll pass the same database connection into another repository
$posts = new PostRepository($database);
$posts->all(); // returns an array of all posts
$posts->getById(123); // returns a specific post by id
In PHP I have:
protected function registerTwigFunctions()
{
return [
'count_VKcomments' => 'countVKCommentsFunction'
];
}
public function countVKCommentsFunction($context, $pdo) {
$url = $context['url'];
$sql = "SELECT id, COUNT(*) FROM level_1 WHERE url = ?";
$count = $pdo->prepare($sql)->execute([$url])->fetchColumn();
return $count;
}
In Twig I have:
{% set url = global.request.uri %}
{{ count_VKcomments({ 'url': url }) }
But it only works if I remove the $pdo argument from countVKCommentsFunction, otherwise some temporary file complains about too few arguments being passed from Twig. But I need to include that $pdo argument just to avoid duplicating connection to the database, which is already defined as $pdo in another function. At the same time, I don't understand how to properly call countVKCommentsFunction provided that it needs $pdo as an argument.
From perspective of Twig your function should only accept/care about what template practically needs to pass to it.
From perspective of PHP implementation your function needs access to state of your application (your database connection in this case).
The solution here is to decouple the two tasks:
Make and instantiate object that has access to the necessary state.
Use the object's method as callback for Twig function.
That way template data comes through arguments and state data comes through implementation.
I ended up creating a separate function that defines $pdo:
private function pdo() {
$core = Core::getInstance();
$pdo = $core->dbh;
return $pdo;
}
Then in every function that needs to use this variable I added $pdo = $this->pdo();
Another approach would be to declare the variable as a class member, but I ended up doing it like I described.
On my site at the beginning of every script I include a "bootstrap" script which queries a few things from the database, does some calculations and then loads the variables into constants that I define one by one.
Some examples are:
define("SITE_ID", $site_id); // $site_id is pulled from a field in the database
define("SITE_NAME", $site_name);
// pulled from a field in the same row as the above
define("STOCK_IDS", $stock_ids);
//computed array of stock id integers from a different query.
//I perform logic on the array after the query before putting it in the definition
define("ANALYTICS_ENABLED", false);
// this is something I define myself and isnt "pulled" from a database
Now, I have many functions on the site. One example function is get_stock_info. And it references the STOCK_IDS constant.
What I want to do is have a class which has the above constants in it and the get_stock_info function.
Would the best approach to be have an empty class "site", create an instance of it and then afterwards define the static variables above one by one? Or is that not a good way and should I move all of of my logic which pulls from the database and calculates SITE_ID, STOCK_IDS, ANALYTICS_ENABLED etc into the constructor instead?
Ultimately I want the class to contain all of the above info and then I would be able to use class methods such as site::get_stock_info() and those methods will have access to the constants via self:: or this.
There's a lot more I want to do than that but that would give me enough to figure the rest out.
I think this approach isn't the best. You should consider not using constants as your values aren't constant. For your case it is better to have a class with classic getters methods.
Something like this:
class SiteInfo
{
private $siteId;
private $siteName;
private $stockIds;
private $analyticsEnabled;
public function __construct()
{
// Results from the database
$results = $query->execute();
$this->siteId = $results['siteId'];
$this->siteName = $results['siteName'];
$this->stockIds = $results['stockIds'];
$this->analyticsEnabled = $results['analyticsEnabled'];
}
public function getSiteId()
{
return $this->siteId;
}
public function getSiteName()
{
return $this->siteName;
}
public function getStockIds()
{
return $this->stockIds;
}
public function isAnalyticsEnabled()
{
return $this->analyticsEnabled;
}
}
I've made this class to handle all of my sql-queries. But I'm unsure of how to use it properly.
The class looks something like this (this is a VERY simple version of it):
class sql {
private $conn;
private $data;
function __construct() {
//makes connection to DB and sets $conn and $data
}
public function select($variables, $table, $criterias) {
//returns an array with all the info from DB
}
function __destruct() {
//closes the sql-connection
}
}
The question now is: Is this going to overload the DB, if I use it multiple times on every page-load? (refered to as Example #1)
$dbInfo = (new sql)->select($var,$tab,$cri);
$moreInfo = (new sql)->select($var2,$tab2,$cri2);
$evenMoreInfo = (new sql)->select($var3,$tab3,$cri3);
Would it be beneficial to make my sql class's methods static?
Or should I not create a new instance of a sql object every time I want to make a query (like the example below - refered to as Example #2)?
$sql = new sql();
$dbInfo = $sql->select($var,$tab,$cri);
$moreInfo = $sql->select($var2,$tab2,$cri2);
$evenMoreInfo = $sql->select($var3,$tab3,$cri3);
How and when is Example #1 the better choice over Example #2, and vice versa?
If I assume that Example #1 is going to take the most resources from the DB, when would you pick Example #1 over Example #2?
Your example 2 is more common to see, however the SQL object is usually a static/singleton. So it connects to the database once per server request.
Your base SQL object should handle connecting to a database and then handle basic input/output, such as executing a string of SQL and returning the results.
You can then add new objects on top of that for each object/table than then interfaces with this SQL singleton. These classes will handle constructing their custom SQL based on their table, joins, field names/types, etc.
E.g:
A very basic 'table' object looks like this
class SomeTableObject
{
m_TableName = 'SomeTable'; // Table to get Data from
function GetSelectSQL()
{
return "SELECT * FROM ".$this->m_TableName;
}
function Select($where)
{
$sql = $this->GetSelectSQL().$where;
return SqlSingleton::Execute($sql);
}
function GetByID($id)
{
$where = " WHERE FieldNameForID=$id";
return $this->Select($where);
}
}
These objects work better if they extend a base class that has those basic GetSelectSQL, TableName, Select, etc functions. The GetByIDs (and other gets, updates, inserts) will vary from table to table.
I use a static function to create a PDO object.
It accepts 2 params:
a string and an object which contains the connection settings (dns, user, pass).
in order to prevent unnecessarily creating duplicate PDO connections with the same name, I tried to create a multi-key dictionary to cache the PDO object in.
Here is what I did:
include_once('IPDOSettings.php');
class PDOManager
{
private static $connections; // array of connections
public static function getConnection(IPDOSettings $settings, $connection_name = 'default')
{
$dictionary_key = array('name' => $connection_name, 'settings' => $settings);
if(!self::$connections[$dictionary_key])
{
$DBH = new PDO($settings->getDNS(),$settings->getUser(),$settings->getPass());
self::$connections[$dictionary_key] = $DBH;
}
return self::$connections[$dictionary_key];
}
}
However after testing this I get this error Illegal offset type. After looking it up I find out that you cannot use objects or arrays as keys.
So is there anyway to do what I am trying to achieve?
Not really an answer to your question but do you expect PDOManager::getConnection() being called multiple times with the same $connection_name but different settings? Do you need to store the settings along with the db handle in your cache?
This problem wouldn´t even occur if you´d just store the connections by name:
// my suggestion/idea: use $connection_name as key
$dictionary_key = $connection_name;
if(!self::$connections[$dictionary_key])
{
$DBH = new PDO($settings->getDNS(),$settings->getUser(),$settings->getPass());
self::$connections[$dictionary_key] = $DBH;
}
return self::$connections[$dictionary_key];
EDIT:
Well, if you cant just use $connection_name as a key, you could combine $connection_name and use spl_object_hash() in order to get your key:
$dictionary_key = $connection_name . spl_object_hash($settings);
This is much nicer then e. g. using serialize() to get a string representation of the $settings object.
Have a look at SplObjectStorage, it allows you to use an object as key.
I would do something like this:
$dictionary_key = $connection_name . $settings->toString();