This question already has answers here:
What is dependency injection?
(37 answers)
Closed 9 years ago.
I know the basics of PHP and have only ever used it to debug WordPress code generally, but now I want to write my own little program to download an email and process an attachment and I have decided to try using classes as I have a basic understanding of OO programming.
SO PLEASE READ: I am a novice! I don't know what on earth dependency injection is or means...
My issue is that I have created a function called printStatus(), so I can toggle on/off output of comments. I was looking at this, and I'm not sure how or if it would fit into a class structure or if I need to include this function in every other class I create?
Basically - If I created a class, I would need to make it available to all other classes (i.e. a global class) but I'm not sure if that is achievable.
My questions are:
Do I have to pass a reference to the printOutput class to and from every class I use to have it available to me OR can I declare it globally to make it available to all classes OR do I need to include the same function in every class I create?
I've created a MySQL Connection class and I am passing that into each object for use - should (can I) declare it globally and just make it available to all classes?
Thanks for the 101.
Here is my code, am I going down the right path?: (see specifically, references to printStatus())
PS - $formatoutput->printStatus() does not work within other classes - I'm looking to understand what structure is required to make it work.
class.formatoutput.php:
class formatOutput {
var $debug = true;
function printStatus($text, $html = true) {
if ($debug) {
echo $text;
echo $html?"<br/>\n":"\n";
}
}
function printObjectStatus($object, $html = true) {
if ($debug) {
echo '<pre>';
echo $text;
echo $html?"</pre><br/>\n":"</pre>\n";
}
}
}
class.connection.php:
class Connection
{
var $db_host = "host";
var $db_name = "name";
var $db_user = "user";
var $db_pass = "pass";
var $db_conn;
function connectToDatabase() {
$db_conn = mysqli_connect($this->db_host, $this->db_user, $this->db_pass, $this->db_name);
if (!$db_conn) {
die('Connect Error (' . mysqli_connect_errno() . ') ' . mysqli_connect_error());
}
else
{
$this->db_conn = $db_conn;
$formatoutput->printStatus( "Connection established");
}
return $this->db_conn;
}
function closeConnection() {
mysqli_close($this->db_conn);
$formatoutput->printStatus( "Connection closed");
}
}
class.customer.php:
class Customer {
var $customer_id;
var $customer_name;
function getCustomer($connection, $user_id) {
$query = "SELECT id, name FROM customer WHERE user_id=$user_id";
$result = mysqli_query($connection, $query);
if($result === FALSE) {
die('Connect Error (' . mysqli_errno() . ') ' . mysqli_error());
}
$row_count = mysqli_field_count($connection);
$formatoutput->printStatus( "COUNT: (".$count.")");
}
}
index.php:
include 'class.formatoutput.php';
include 'class.connection.php';
include 'class.customer.php';
$formatoutput = new formatOutput();
$formatoutput->printStatus('Start new Connection()');
$connection = new Connection();
$connection->connectToDatabase();
$customer = new Customer();
$customer->getCustomer($connection->db_conn, "1");
$connection->closeConnection();
Declare the function printStatus as static:
static function printStatus($text, $html = true)
You can call this function by using "::",
formatOutput::printStatus("hello");
Related
I'm coming from Java programming and I'm trying to apply my knowledge in OOP style programming in PHP.
So, I tried to create a utility class to connect to database just like how I usually do it in Java where I create a static method to get the database connection.
However, after spending hours I still can't fix the error.
DBHelper.php
<?php
class DBHelper
{
protected $db_name = 'myDb';
protected $db_user = 'root';
protected $db_pass = '';
protected $db_host = 'localhost';
public function obtainConnection()
{
$mysqli_instance = new mysqli($this->db_host, $this->db_user, $this->db_pass, $this->db_name);
/* check connection */
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}
return $mysqli_instance;
}
}
?>
There are no errors in this file
Then I tried to use it on another file called login.php
login.php
<?php
if (isset($_POST['submit'])) {
include "/DBUtility/DBHelper.php";
$username = $_POST['username']; //s means string
$password = $_POST['password']; // s means string
echo "<br/> Username value: " . $username;
echo "<br />Password value: " . $password;
}
if (empty($username) || empty($password) ) {
echo "Fill out the fields!";
} else {
//PREPARE THE PreparedStatment or Stored Procedure
$dbHelper = new DBHelper();
$connection = $dbHelper->obtainConnection();
$preparedStatement = $connection->prepare('CALL getUserRoleByLogin(?, ?)'); //getUserRoleByLogin() is the name of stored proc in mysql db
$preparedStatement->bind_param('ss', $username, $password); //assign arguments to ? ?
$preparedStatement->execute();//execute the stored procedure. This will return a result
$userRole = $preparedStatement->store_result();
$countOfRows = $preparedStatement->num_rows;
?>
I read every related question about the Fatal error: Cannot redeclare class CLASSNAME error. I tried following the instructions given by many which is to use require_once("DBHelper.php"); instead of include("DBHelper.php");
but still can't get rid of the error.
I tried making the obtainConnection() static and called it via DBHelper::obtainConnection(); but with no luck. Same error message.
I get the error on opening brace of class DBHelper {
I hope you can help me with this.
Thank you.
A couple tips you should do when doing OOP in PHP:
1) I would maybe rethink about not baking the db credentials into your class directly, it makes it harder/more cumbersome to modify them via UI if you wanted to implement a UI control mechanism down the line. Instead, try making a define or maybe a json pref file or a dynamically-created php file that contains an array, something like that. I will do a define because it's the easiest to demonstrate:
/config.php
# You can create a series of defines including the database
define('DB_HOST','localhost');
define('DB_NAME','dbname');
define('DB_USER','root');
define('DB_PASS','dbpassword');
# To maximize compatibility it's helpful to define fwd/back slash
define('DS',DIRECTORY_SEPARATOR);
# It is helpful to create path defines for easy file inclusion
define('ROOT_DIR',__DIR__);
define('CLASSES',ROOT_DIR.DS.'classes');
# Start session
session_start();
2) Create a class autoloader in the config.php file which then allows you to not have to manually include/require classes in pages. It will automatically include them:
spl_autoload_register(function($class) {
if(class_exists($class))
return;
# This will turn a namespace/class into a path so should turn:
# $db = new \DBUtility\DBHelper();
# into:
# /var/www/domain/httpdocs/classes/DBUtility/DBHelper.php
$path = str_replace(DS.DS,DS,CLASSES.DS.str_replace('\\',DS,$class).'.php');
# If the class file is located in the class folder, it will include it
if(is_file($path))
include_once($path);
});
3) I am going to create a static connection so you don't create a new connection every time (also I will use PDO):
/classes/DBUtility/DBHelper.php
<?php
namespace DBUtility;
class DBHelper
{
protected $query;
private static $con;
public function connection()
{
# This will send back the connection without making a new one
if(self::$con instanceof \PDO)
return self::$con;
# I like to catch any pdo exceptions on connection, just incase.
try {
# Assign the connection
self::$con = new \PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME,DB_USER,DB_PASS);
}
catch(\PDOException $e) {
# Here you can just die with a more user-friendly error.
# It would be helpful to save the actual error to a log file
$msg = $e->getMessage();
# I would put your log outside the root or in a protected folder
$txt = realpath(ROOT_DIR.DS.'..').DS.'errors'.DS.'sql.txt';
# Make a directory if none set
if(!is_dir(pathinfo($txt,PATHINFO_DIRNAME))) {
# Make the directory
if(mkdir(pathinfo($txt,PATHINFO_DIRNAME),0744,true)) {
# Save to log file
file_put_contents($txt,$msg.PHP_EOL);
}
}
else {
# Save to log file
file_put_contents($txt,$msg.PHP_EOL);
}
die("Site is under maintenance.");
}
}
# It would be helpful to create a query that will bind and not bind
public function query($sql,$bind = false)
{
if(is_array($bind)) {
foreach($bind as $key => $value) {
$sKey = ":{$key}";
$bindArr[$sKey] = $value;
}
$this->query = $this->connection()->prepare($sql);
$this->query->execute($bindArr);
}
else {
# The second "query" on this is the method from PDO, not the
# "query" method from this class
$this->query = $this->connection()->query($sql);
}
return $this;
}
public function getResults()
{
if(empty($this->query))
return false;
while($result = $this->query->fetch(\PDO::FETCH_ASSOC)) {
$row[] = $result;
}
return (isset($row))? $row : false;
}
}
# If your page ends with a php tag, you should just remove it. It will
# protect against empty spaces that may cause "header already sent" errors
3a) I use something similar to this to autoload functions:
/classes/Helper.php
class Helper
{
public static function autoload($function)
{
if(function_exists($function))
return;
$path = ROOT_DIR.DS.'functions'.DS.$function.'.php';
if(is_file($path))
include_once($path);
}
}
4) Create useful/reusable functions or class/methods
/functions/getUserRole.php
function getUserRole($username,$password,\DBUtility\DBHelper $DBHelper)
{
return $DBHelper->query('CALL getUserRoleByLogin(:0, :1)',array($username,$password))->getResults();
}
/index.php
# Include the config file
require_once(__DIR__.DIRECTORY_SEPARATOR.'config.php');
if (isset($_POST['submit'])) {
# No need for this line ->> include "/DBUtility/DBHelper.php";
# Use trim to remove empty spaces on the left and right
$username = trim($_POST['username']);
$password = trim($_POST['password']);
}
if (empty($username) || empty($password) ) {
echo "Fill out the fields!";
} else {
# User our function autoloader to include this function
Helper::autoload('getUserRole');
# Use the function and inject the DB class
$userRoles = getUserRole($username,$password,new \DBUtility\DBHelper());
$count = count($userRoles);
echo "Count: {$count}";
echo '<pre>';
print_r($userRoles);
echo '</pre>';
}
I've been working on a 3-tier architecture and would like some reassurance that my approach is correct as this is for a large project that hopefully will convert 10 years of spaghetti code into an organized system.
The code below has been separated into three layers and it works great so far. The API (works together with some HTML & CSS and AJAX as the presentation layer), Business Logic Layer, and a Data Access Layer. I'm using Slim as you can see for the API.
A few questions I have are:
In general, is this a proper way to get the layers working together?
Should the SQL query itself "SELECT x, y, z FROM ..." be contained in the business layer as below, or in the DataLayer, or are there times you may require SQL in both BLL and DAL? I've seen both, but restricted to BLL seems like a more logical separation.
In the example below you can see I'm getting users by company_id. What if I wanted by company_id and isadmin? Would best practice be to associate another route and method for that, such as /users/company/:id/admins with another method such as getAdminUsers()? Doesn't seem right as I could pass parameters to getUsers($id, $isadmin), but not sure what is best practice here.
API
<?php
require $_SERVER["DOCUMENT_ROOT"] . '/BLL/BLL.php';
require 'Slim/Slim.php';
$app = new Slim();
$app->get('/users/company/:id', 'getUsers');
$app->get('/users/:id', 'getUser');
$app->post('/users', 'addUser');
$app->put('/users/:id', 'updateUser');
$app->delete('/users/:id', 'deleteUser');
$app->run();
function getUsers($id) {
$bll = new BusinessLayer();
$result = json_encode($bll->getAllUsersBLL($id));
echo '{"user": ' . $result . '}';
}
?>
Business Logic Layer
<?php
require_once $_SERVER["DOCUMENT_ROOT"] . "/DAL/DAL.php";
class BusinessLayer
{
var $dal;
function __construct() {
$this->dal = new DataLayer();
}
public function getAllUsersBLL($id) {
$sql = "SELECT * FROM users WHERE company_id = :id ORDER BY id";
$ret = $this->dal->query($sql, $id);
return $ret;
}
}
}
?>
Data Access Layer
<?php
class DataLayer
{
public function connect() {
$dbhost = "localhost";
$dbuser = "root";
$dbpass = "";
$dbname = "testdatabase";
$db = new PDO("mysql:host=$dbhost;dbname=$dbname", $dbuser, $dbpass);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $db;
}
public function query($sql, $id) {
try {
$db = $this->connect();
$stmt = $db->prepare($sql);
$stmt->bindParam("id", $id);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
$db = null;
return $result;
} catch(PDOException $e) {
echo '{"error":{"text":'. $e->getMessage() .'}}';
}
}
}
?>
Any other feedback is more than welcome!
I am working on a user database and have it set up so that the dropdown menus in my registration form take their values from tables in the database. The problem is that when I tested it out, the dropdowns were empty. Naturally the first thing I did was to go and check my connection. To connect to the database, I have a file called BaseDAO.php which contains the following:
<?php
class BaseDAO {
const SQL_HOST = "localhost";
const SQL_USER = "user6538";
const SQL_PW = "sdWf234Fg";
const SQL_DB = "userDB";
protected $con = null;
function connect() {
$this->con = mysql_connect(self::SQL_HOST, self::SQL_USER, self::SQL_PW);
if(!$this->con) {die('Could not connect to MySQL host: ' . self::SQL_HOST . ' Error: ' . mysql_error()); }
echo "Connected!"; //NOTE: I only just added this to test it
mysql_select_db(self::SQL_DB);
}
function close() {
mysql_close($this->con);
}
}
?>
So to test it, I added echo "Hello World!"; outside of the class, just as a reference, then I added connect(); right below it. I opened my page and only saw the Hello World! ... So then I tried echo connect(); and again nothing. Then I tried this:
$baseDAO = new BaseDAO();
$baseDAO->connect();
And still there was nothing. Then I commented out the entire class and just added this:
<?php
//...
//Commented out class
//....
function connect() {
$con = mysql_connect("localhost", "user6538", "sdWf234Fg" );
if(!$con) { echo "3"; die('Could not connect: ' . mysql_error()); }
echo "Connected!";
}
echo "Hello World!";
connect();
?>
Lo and behold the output was this:
Hello World!Connected!
I don't understand why my class doesn't work... It explains why my dropdowns were not working, since their DAO files require my BaseDAO.
Would someone care to enlighten me on what's going on here? Why won't my class work?
NOTE: I am using a friend's old project as a guide, so theoretically my BaseDAO.php should work, since his worked.
EDIT: Corrected the arguments in the second connect() function
The return value of mysql_connect is FALSE in an error case. 0 is a positive value.
To be sure, make this change:
if(self::con === FALSE) {die('Could not connect to MySQL host: ' . self::SQL_HOST . ' Error: ' . mysql_error()); }
Your second SQL, where you did remove the CLASS parts, cannot work, but it strangely does.
$con = mysql_connect(self::SQL_HOST, self::SQL_USER, self::SQL_PW);
You do not have the self:: constants defined if you class does not exist. PHP should have produced an error message in this case. So your description is wrong and missing something.
class BaseDAO {
private static $SQL_HOST = "localhost";
private static $SQL_USER = "user6538";
private static $SQL_PW = "sdWf234Fg";
private static $SQL_DB = "userDB";
protected static $con = null;
public static function connect() {
self::con = mysql_connect(self::SQL_HOST, self::SQL_USER, self::SQL_PW);
if(!self::con) {die('Could not connect to MySQL host: ' . self::SQL_HOST . ' Error: ' . mysql_error()); }
echo "Connected!"; //NOTE: I only just added this to test it
mysql_select_db(self::SQL_DB);
}
public static function close() {
mysql_close(self::con);
}
}
To connect do the following no need to create an instance of the class because its static:
BaseDAO::connect();
I've got a problem with include. I'm doing some kind of blog, and at this moment it looks like this:
index.php
article.php
class/art.class.php
Let's focus on article.php, which looks like this:
<?php
$mysqli = new mysqli("","","",""); // here are my connection details
if ($mysqli->connect_error) {
die('Connect Error (' . $mysqli->connect_errno . ') '
. $mysqli->connect_error);
}
$mysqli->query("SET NAMES 'utf8'");
require("class/art.class.php");
$art = new Article();
print_r($art->get_art(trim($_GET['id'])));
$mysqli->close();
?>
And art.class.php is like this:
<?php
class Article {
function get_art($id) {
if(!is_numeric($id)) {
header("Location: index.php");
die("<h2>ID isn't numeric, cannot go on.</h2>'");
}
if($result = $mysqli->query("SELECT * FROM `articles` WHERE id='$id';")) {
while($row = $result->fetch_array(MYSQLI_ASSOC)) {
$art = $row;
}
$result->close();
}
return $art;
}
}
?>
The problem is a response from MySQL. Sorry, I mean no response. And no errors. I figured out that I need to add mysql connection code to class code. But why? How I can connect once to database and call it from anywhere, even from included class?
And sorry if my english is bad..
The get_art function within the Article class does not have access to variables outside of it's scope: please see the answer here.
In order to fix your issue, you may provide access to the $mysqli object by passing it to the constructor of the Article class when you instantiate it:
Article.php:
$mysqli = new mysqli("","","",""); // your connection details
$art = new Article($mysqli);
art.class.php:
class Article {
protected $mysqli;
public function __construct($mysqli) {
$this->$mysqli = $mysqli;
}
function get_art($id) {
// Replace $mysqli with $this->mysqli everywhere you need to
// make database calls
}
}
Although some would recommend that you avoid doing so, you could use PHP's $GLOBALS variable to store your database connection:
$mysqli = new mysqli("","","",""); // here are my connection details
if ($mysqli->connect_error) {
die('Connect Error (' . $mysqli->connect_errno . ') '
. $mysqli->connect_error);
}
$GLOBALS['mysql'] = $mysqli;
This way you would have access to it within your class:
class Article
{
function get_art($id)
{
$mysqli = $GLOBALS['mysqli'];
...
}
}
In anticipation of mysql_query being deprecated PHP 5.5.0, I have been working on a class to handle all my DB queries :
class DataBaseClass {
//.....some other function and variables declared here....
function GetConnection() {
try {
$this->conn = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME, DB_USER, DB_PASS);
$this->conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
}
catch(PDOException $e) {
echo $e->getMessage();
}
return $this->conn;
}
function Query($str_sql, $arr_parameters = array()) {
try {
$this->str_mysql_error = $this->int_num_rows = $this->int_num_affected_rows = $this->int_mysql_insert_id = '';
if (count($arr_parameters) > 0) {
$obj_result = $this->conn->prepare($str_sql);
$obj_result->execute($arr_parameters);
} else {
$obj_result = $this->conn->query($str_sql);
}
}
catch(PDOException $e) {
$this->str_mysql_error = $e->getMessage() . $str_sql;
}
}
}
Then I have another class to create new user:
class AddNewUser {
//.....some other function and variables declared here....
function InsertUser() {
$str_sql = "INSERT INTO (uname, name, email, pass, user_regdate, theme) VALUES )";
$_SESSION['db_connection']->Query($str_sql, '');
}
}
Now on my main user creation page I have :
$_SESSION['db_connection'] = new DataBaseClass;
//Reason I used $_SESSION to store my DB object, is so that it can be accessible everywhere.
//Did not want to use "global" everywhere. Not sure if this is he best way???
$cls_new_user = new AddNewUser ();
$cls_new_user->InsertUser(); //Does not raise PDOExecption although SQL cleary wrong inside this method
if ( $_SESSION['db_connection']->str_mysql_error) {
//show error in error div
}
$str_sql = "SELECT some wrong SQL statment";
$_SESSION['db_connection']->Query($str_sql); // This does raise PDOExecption
if ( $_SESSION['db_connection']->str_mysql_error) {
//show error in error div
}
I'm not sure why the DB class function "Query" would not raise an exception on clearly wrong SQL when called from another class. But same function called from main page code (not inside function / class) raises and exception error.
Also, the "InsertUser" function does not execute / insert anything into DB even if SQL correct.
Could it be scope related, or the fact that I'm trying to enforce global scope of my DB object by putting it in $_SESSION ??
Am I going about this the wrong way? Reason for going class route to encapsulate all my DB calls was to avoid any deprecation issues in future - only having to update class.
Make your function this way.
function Query($str_sql, $arr_parameters = array()) {
$stmt = $this->conn->prepare($str_sql);
$stmt->execute($arr_parameters);
}
I am pretty sure that exception would be thrown
The only issue can be with catching exceptions, not throwing. And it could be caused by Namespace, not scope. To be certain, you can always prepend all PDO calls with a slash:
\PDO::FETCH_ASSOC
\PDOException
etc.