I have been trying to get better at OOP. I am writing a database class, which will handle simply connecting to a database via PDO. Now, I want to make it so that it uses different variables if it is being served from my localhost server.
Consider the following code:
<?php
class Database {
private $host;
private $name;
private $username;
private $password;
public $conn;
if ($_SERVER['SERVER_NAME'] == "localhost") {
$host = "change_to_your_db_host";
$name = "change_to_your_db_name";
$username = "change_to_your_db_username";
$password = "change_to_your_db_password";
}
else {
$host = "change_to_your_db_host";
$name = "change_to_your_db_name";
$username = "change_to_your_db_username";
$password = "change_to_your_db_password";
}
public function connect () {
$this->conn = null;
try {
$this->conn = new PDO("mysql:host=" . $this->host . ";dbname=" . $this->db_name, $this->username, $this->password);
}
catch (PDOException $exception) {
echo "Connection error: " . $exception->getMessage();
}
return $this->conn;
}
}
?>
I am very new to classes in general - I have been writing pure prodecural function-based PHP for wayyyy too long.
My questions are:
Is it cool to use $_SERVER variables within a class like that?
Is using that conditional statement within the class to determine the private variables OK? This class will be included in all of my other scripts which access the database via my object classes.
Is there a more efficient way to write this rather that have it echo an exception if one is caught?
I just want to make sure that I am doing things properly going forward. I have been writing PHP for a long time and I want to get out of the my outdated and archaic methods entirely.
You should do your conditional declaration in the __construct() method of your class like so:
<?php
class Database {
private $host;
private $name;
private $username;
private $password;
public $conn;
public function __construct()
{
if ($_SERVER['SERVER_NAME'] == "localhost")
{
$this->host = "change_to_your_db_host";
$this->name = "change_to_your_db_name";
$this->username = "change_to_your_db_username";
$this->password = "change_to_your_db_password";
}
else
{
$this->host = "change_to_your_db_host";
$this->name = "change_to_your_db_name";
$this->username = "change_to_your_db_username";
$this->password = "change_to_your_db_password";
}
}
public function connect () {
$this->conn = null;
try {
$conn = new PDO("mysql:host=" . $this->host . ";dbname=" . $this->db_name, $this->username, $this->password);
}
catch (PDOException $exception) {
throw $exception // you can throw again this \Exception to handle it in your code using the object
}
$this->conn = $conn;
return $this; // you should return $this so you can chain the object methods. Since $con is public, you can still access it
}
}
Is it cool to use $_SERVER variables within a class like that?
I cannot see why not.
Is using that conditional statement within the class to determine the private variables OK? This class will be included in all of my other scripts which access the database via my object classes.
As per #MarcB's comment, no you cannot run code at the top-level of a class, only in methods.
Is there a more efficient way to write this rather that have it echo an exception if one is caught?
Yes, throw it again so you can handle it in the final code using the Database class.
Related
I currently have a class which creates one database connection, however I would like to create another connection too. I have tried copying the class structure but just renaming the variables and functions however that doesn't work and it seems like it doesn't detect where my new PDO connection is as I get error Uncaught Error: Call to a member function prepare() on null in. What is the best approach to take in my case when creating 2 database connections?
config.php:
<?php
class Database
{
private $host = "localhost";
private $db_name = "database1";
private $username = "root";
private $password = "";
public $conn;
public function dbConnection()
{
$this->conn = null;
try
{
$this->conn = new PDO("mysql:host=" . $this->host . ";dbname=" . $this->db_name, $this->username, $this->password);
$this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch(PDOException $exception)
{
echo "Connection error: " . $exception->getMessage();
}
return $this->conn;
}
}
?>
class.user.php:
class USER
{
private $conn;
public function __construct()
{
$database = new Database();
$db = $database->dbConnection();
$this->conn = $db;
}
public function runQuery($sql)
{
$stmt = $this->conn->prepare($sql);
return $stmt;
}
}
You could create your User class and pass the connection to the constructor. This will give you flexibility to swap out the connection.
Regarding your database class, it seems to be a wrapper, to create a PDO connection. You could do away with it, or extend the class with different params.
Perhaps look at dependency injection, and containers. They might help you here.
<?php
class User
{
private $conn;
public function __construct(PDO $conn)
{
$this->conn = $conn;
}
public function runQuery($sql)
{
$stmt = $this->conn->prepare($sql);
return $stmt;
}
}
PHP programming is pretty new to me - so I hope that I don't ask something stupid. However, I could not find any answer to my problem :-(
I have saved my database connection in a separate .php file. Let's say "connection.inc.php"
connection.inc.php looks as follows:
$host = "name.server.com";
$dbname = "foo";
$user = "bar";
$pass = "PaSW0rd";
try
{
$DBH = new PDO("mysql:host=$host;dbname=$dbname",$user,$pass,
array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));
}
catch(PDOException $e)
{
...
}
In my index.php I would include the connection.inc.php with 'include_once "connection.inc.php"
Additionally, I have a class called house.class.php in which I have a method with an SQL query to my database. I call the method via 'getColor($id, $DBH)' from my index.php
Is there any way to use the method without always putting $DBH into the call?
Thanks for your help!
Let's say you have a class named House with a getColor() method, and in your index.php. You can use the keyword global inside your function/method, but I am strongly against this. Example:
public function getColor(int $id)
{
global $dbh;
//and use it as $dbh
$dbh->prepare(....
}
Or you can inject it in the constructor. Example of how your class will look like
class House
{
private $dbh;
function __construct($dbh)
{
$this->dbh = $dbh;
}
public function getColor($id)
{
//$this refers to current object context
$this->dbh->prepare.....
}
}
And when you instantiate the House object you will pass the $DBH into the constructor. Example for your index.php:
include_once "connection.inc.php";
$house = new House($DBH);
$house->getColor($id);
Although, I suggest that you checkout Domain Driven Design, Factory and Repository pattern, and ORM like Doctrine or Eloquent to get more understanding on how to separate your application/domain logic from the database.
You should get the $DBH from the class itself rather than in the index file. Then you can have that instance of the database connection available to use class wide with other methofs in that class as well without having to re-connect for every function or having to provide every function the connection as an argument.
An example of this would look something like this:
house.class.php
<?php
class House{
private $DBH;
function __construct(){
include_once "connection.inc.php";
$this->DBH = $DBH;
}
function getColor($id){
$this->DBH // <---- use it like this instead of your $DBH old variable.
}
}
?>
class Database
{
private $host = "localhost";
private $dbname = "codeignitor";
private $username = "root";
private $password = "";
public $conn;
public function getConnection()
{
$this->conn = null;
try {
$this->conn = new PDO ("mysql:host=" . $this->host . ";dbname=" . $this->dbname, $this->username, $this->password);
$this->conn->exec("set names utf8");
// echo "MySqlConnected";
} catch(PDOException $exception){
echo "Connection error: " . $exception->getMessage();
}
return $this->conn;
}
}
I want to access a connexion database through PDO in a static way but I am still getting an error. This is my code :
require_once 'include/class.Settings.php';
class MonthlySums{
protected static $dbh;
public function __construct() {
$user = Settings::$db['user'];
$pass = Settings::$db['password'];
$dsn = 'mysql:host=' . Settings::$db['host'] . ';dbname=' . Settings::$db['db'];
try {
self::$dbh = new PDO($dsn, $user, $pass);
} catch(PDOException $e) {
die("Error! : " . $e->getMessage());
}
}
public static function get($init_id) {
$sql = "SELECT `year`, `month`, `gains_monthly_sum` FROM `fxs_gain_sums` WHERE `init_id` = '$init_id'";
$resultats = MonthlySums::$dbh->query($sql);
var_dump($resultats);
}
}
I have this error :
Fatal error: Call to a member function query() on a non-object in /home/public_html/gainlosssums.php on line 45
That line is : $resultats = MonthlySums::$dbh->query($sql);
How to make it work a static way ?
When you call a static method in a class, the constructor is not automatically invoked like when you create an instance of the class. With your code you would have to create at least one instance of the class so that the database connection would be set in your static variable. Then you would be able to call it in your desired static way.
If you still want to continue with your code, you could change it to this:
(Please note: this is the OP's modified code and I am not going into other aspects - just the result to the OP's question/problem)
require_once 'include/class.Settings.php';
class MonthlySums{
protected static $dbh;
public function __construct() {
$this->databaseLogin();
}
public static function databaseLogin(){
$user = Settings::$db['user'];
$pass = Settings::$db['password'];
$dsn = 'mysql:host=' . Settings::$db['host'] . ';dbname=' . Settings::$db['db'];
try {
self::$dbh = new PDO($dsn, $user, $pass);
} catch(PDOException $e) {
die("Error! : " . $e->getMessage());
}
}
public static function get($init_id) {
$sql = "SELECT `year`, `month`, `gains_monthly_sum` FROM `fxs_gain_sums` WHERE `init_id` = '$init_id'";
$resultats = self::$dbh->query($sql);
var_dump($resultats);
}
}
MonthlySums::databaseLogin();
As you can see there is a new static method databaseLogin and is called right after the class declaration. This way the static variable for the database connection is set and can be used in other methods.
You now can call it this way:
MonthlySums::get(1234);
Again, this might not be best practice but a solution to your problem.
function __construct($name, $lastName, $address, $rollNo)
{
$this->name = $name;
$this->lastName = $lastName;
$this->address = $address;
$this->rollNo = $rollNo;
$this->conn = new \PDO('mysql:host=localhost;dbname=students', 'root', '');
}
public function getUser($id){
$sql = "SELECT * FROM students WHERE id = ".intval($id);
foreach($this->conn->query($sql) as $row){
$user['name'] = $row['name'];
$user['address'] = $row['address'];
$user['roll_no'] = $row['roll_no'];
}
return $user;
}
So what i am trying to learn is how i can use a single PDO object in my all classes rather than creating $conn in all classes like users, courses etc.
I've came across the words Dependency Injection , Singleton , Factory and for me as a beginner these are all mixed up.
Also i want to know is it a bad practice what i am doing in my code for creating new objects for PDO.
A singleton is a control class that will ensure only one instance of a given object will exist at a time. So instead of calling $conn = new PDO() in every object, creating multiple instances of PDO(), you can write a singleton class named Database and use $conn = Database::instance() instead.
class Database
{
static $instance = null;
static function instance()
{
if (self::$instance == null)
self::$instance = new PDO('mysql:host=localhost;dbname=students', 'root', '');
return self::$instance;
}
}
Basically what it does is create a PDO() class if it doesn't exist, and reuse it if it was already instanced before.
As a side advantage, you also won't need to have your database credentials spread all over the project. The class Database will have it and noone else.
You could make a class called say 'Database' to deal with connecting to MySQL and setting up the PDO.
so something like...
class Database{
private $host = DB_HOST;
private $user = DB_USER;
private $pass = DB_PASS;
private $dbname = DB_NAME;
private $dbh;
private $error;
public function __construct(){
// setup dsn
$dns = 'mysql:host=' . $this->host . ';dbname=' . $this->dbname;
try{
$this->dbh = new PDO($dsn, $this->user, $this->pass);
}
catch(PDOException $e){
$this->error = $e->getMessage();
}
}
}
And then in your other classes just assign the class to a variable in your other classes so..
$db = new Database();
That would avoid your code duplication. You could also create functions in the Database class to carry out PDO queries as well.
Create a parent class with the connection in it and make a child class that extends the parent class
class Main {
protected $dbh;
function dbConnect() {
$options = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION);
//DB_HOST, DB_NAME, DB_USER, DB_PASS are set via define()
//e.g. define("DB_HOST", "localhost");
$dsn = 'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME;
$this->dbh = new PDO($dsn, DB_USER, DB_PASS, $options);
}
Then..
class User extends Main {
function crudRead() {
parent::dbConnect(); //called from the main class
$db = $this->dbh->prepare('SELECT * FROM user');
$db->execute();
return $db->fetchAll(PDO::FETCH_ASSOC);
}
}
I have been recently toying with dynamic PDO connection methods. However I run into some problems when I started to use other classes.
Why can't I connect to database in Admin class with method that has been constructed in Server class?
I have tried many solutions. This one seemed most logical to me ...
How do I make it work so that I wouldn't have to construct connection in every class?
class Server
{
private $hostdb = 'blah';
private $namedb = 'blah';
private $userdb = 'blah';
private $passdb = 'blah';
public static $conn;
public $errorMessage = 'If you read this text, contact web administrator and tell him about your problem.';
public function __construct()
{
$this->connect();
}
public function connect()
{
try {
$this->conn = new PDO("mysql:host=$this->hostdb; dbname=$this->namedb", $this->userdb, $this->passdb, array(PDO::ATTR_PERSISTENT => true));
$this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->conn->exec("SET CHARACTER SET utf8");
} catch (PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
}
}
And the Admin class:
class Admin extends User
{
function someFunction($table)
{
try {
$sql = "SELECT * FROM $table";
//I want to change this line so that my connection would work
$result = Server::$conn->query($sql);
while ($row = $result->fetch(PDO::FETCH_NUM)) {
//Do something
}
} catch (PDOException $e) {
//Show when debugging
//echo $e->getMessage();
echo Server::errorMessage;
}
}
}
I have instantiated both Server, User and Admin class in config.req.php file.
When I changed "Server::$conn->" to "static::$conn->" it still gave me an error.
Make sure you have instantiated Server at least once in the Request. Otherwise, connect() will never be called. Also, $this->conn will create a new public instance property. Static properties need to be set with static::$conn or self::$conn.
So change your connect() method to
public function connect()
{
try {
self::$conn = new PDO("arguments …"));
self::$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
self::$conn->exec("SET CHARACTER SET utf8");
// … shortened for brevity
On a side note, why not just use Dependency Injection? That makes for more maintainable and testable designs, e.g. keep the $this->conn and remove all the static stuff instead (in addition to the constructor) for something like
class Server
{
private $hostdb = 'blah';
private $namedb = 'blah';
private $userdb = 'blah';
private $passdb = 'blah';
private $conn;
public function getConnection()
{
if (!isset($this->conn)) {
$this->connect();
}
return $this->connection;
}
And then for your Admin class:
class Admin extends User
{
private $server;
public function __construct(Server $server)
{
$this->server = $server;
}
function someFunction($table)
{
try {
$sql = "SELECT * FROM $table";
$result = $this->server->getConnection()->query($sql);
On another side note:
$sql = "SELECT * FROM $table"
Interpolating strings into a query makes you open to SQL Injection attacks. Use prepared statements instead.