Variable scope in OOP - php

I've got the classes in my structure:
require_once("classes/config.php");
require_once("classes/database.php");
require_once("classes/template.php");
require_once("classes/route.php");
require_once("classes/user.php");
$config = new config;
$route = new route();
$db = new database ($config['db_host'], $config['db_name'], $config['db_user'], $config['db_pasw']);
Class 'template' uses template files.
What is the right way to work with database or user class, for example, in the template class?
How it's working now:
global $user;
But it's not 'good coding practices', right?

So, in User class you can do something like this object-oriented way:
class User{
private $user;
public function getUser(){
return $this->user;
}
}
And to get user in your script,
$userObj = new User();
$user = $userObj->getUser();

Dependency injection.
Collect the data that you want to render in your template in a data set that will be injected into the template render function, like:
// This can be class or array, as you prefer
$viewData = array(
'user' => $user,
'something_else' => ...
);
$template = new Template('id');
$template->render($viewData);

Yes it's not very good.
You can specify the connection variables in a separate configuration class.
Then, connect it to the constructor where necessary and use it.
Thus, there will not be a single global variable in your project.
Here is an example. But you must know. It's a bicycle, just for example. But here there is no DI (Dependency injection):
Config
namespace Model;
class ConfigModel
{
public $db = 'name_db';
public $user = 'user_u';
public $pass = '123456';
public $protocol = 'https:';
public $brand = 'MyBrand.com';
public $salt = 'ougfhdsihgsugsdopgf';
public $path = __DIR__;
}
DB class
namespace Model;
class DB
{
protected $host = 'localhost';
protected $db = '';
protected $charset = 'utf8';
protected $user = '';
protected $pass = '';
public function __construct() {
$config = new ConfigModel();
$this->db = $config->db;
$this->user = $config->user;
$this->pass = $config->pass;
$dsn = "mysql:host=$this->host;dbname=$this->db;charset=$this->charset";
$opt = array(
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC
);
try {
$this->DBconnect = new \PDO($dsn, $this->user, $this->pass, $opt);
}
catch( PDOException $Exception ) {
echo $Exception->getMessage();
echo $Exception->getCode();
if(!empty($Exception->getCode())){
die('Error');
}
}
}
public function showAllUser() {
$db = $this->DBconnect->prepare('SELECT `mail` FROM `users`');
$db->execute();
return $db->fetchAll();
}
}

I don't understand, doyou want to render global the user information like as, for example, the name of the user?I think the correct way to use the user details or the login details is using $_SESSION and after with $_SESSION['user'] you can render the user details in template.

Related

Call to a member function prepare() on null OOP

Having trouble understanding classes and inheritance:
core.php:
$servername = "****";
$database = "****";
$username = "****";
$password = "****";
try {
$pdo = new PDO("mysql:host=$servername;dbname=$database", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
echo "Connection failed: " . $e->getMessage();
}
class Database {
protected $pdo;
public function __construct($pdo) {
$this->pdo = $pdo;
}
}
class User extends Database {
private $ip;
private $sessionId;
public function __construct($ip, $sessionId) {
$this->ip = $ip;
$this->sessionId = $sessionId;
}
public function getSessionInfo () {
$stmt = $this->pdo->prepare(".."); <-- error here
....
}
}
When calling:
require_once 'api/core.php';
$database = new Database($pdo);
$user = new User($_SERVER['REMOTE_ADDR'], $_SESSION['info']['id']);
In this contest $database, and $user variables are not related to each other:
require_once 'api/core.php';
$database = new Database($pdo);
$user = new User($_SERVER['REMOTE_ADDR'], $_SESSION['info']['id']);
Thus, calling prepare() on $user won't work.
You need a mechanism, at least like this , although not a good practice to assign Database to a User:
$user->setDatabase($database);
Instead create a static Database object, initiate it before User initiation, and call it statically within User object, or any other object, make it available for all objects.
A quick fix would look like this, where User doesn't extend Database, because it's wrong. User is not a Database.
$database = new Database();
$user = new User();
$user->setDatabase($database); //sets $db variable inside User
//User.php
namespace MyApp;
class User{
private Database $db;
public function setDatabase($db){
$this->db = $db;
}
public function doSomething(){
$this->db->getPdo()->prepare('..');
}
}
//Database.php
namespace MyApp;
class Database{
private $pdo; //returns PDO object
function __construct(){
//create pdo connection
$this->pdo = ..
}
function getPdo(){
return $this->pdo;
}
}
Database should be injected to objects or used by objects, you shouldn't be extending Database just to have it. If you want to do it properly, in an object-oriented way.
Remember PHP doesn't allow multiple inheritances by extend. Tomorrow, you might want to have a Person class that every User will extend, but since you did it wrong in the beginning, and wasting precious extend on Database, it won't be possible. And by not having a control of how many database instances you have created, you will run into issues. You need to know for sure that you have only a single connection object for one database, if of course the opposite is a must - which in your case I doubt.
Of course this will change if you have multiple database requirements, and more sophisticated app structure.
You are receiving this error because User Instance has pdo empty. try this code
$servername = "****";
$database = "****";
$username = "****";
$password = "****";
try {
$pdo = new PDO("mysql:host=$servername;dbname=$database", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
echo "Connection failed: " . $e->getMessage();
}
class Database {
protected $pdo;
public function __construct($pdo) {
$this->pdo = $pdo;
}
}
class User extends Database {
private $ip;
private $sessionId;
public function __construct($pdo, $ip, $sessionId) {\
parent::__construct($pdo)
$this->ip = $ip;
$this->sessionId = $sessionId;
}
public function getSessionInfo () {
$stmt = $this->pdo->prepare("..");
....
}
}
then
require_once 'api/core.php';
$user = new User($pdo, $_SERVER['REMOTE_ADDR'], $_SESSION['info']['id']);
hope it helps.

How to properly use dependency injection with PHP?

I'm very new at PHP. I have two classes: Database and RetrieveItem. Because RetrieveItem needs a connection, I've just been extending the Database class to use its constructor. Apparently this is wrong, because RetrieveItem is not a database?
Here is my current code:
class Database {
public $host = '127.0.0.1';
public $username = 'root';
public $password = '';
public $dbname = 'example';
function __construct(){
$this->connect = new PDO("mysql:host=$this->host;dbname=$this->dbname", $this->username, $this->password);
}
}
class RetrieveItem extends Database {
function retrieve_item(){
$query = $this->connect->prepare("SELECT * FROM posts");
$query->execute();
$all_items = $query->fetchAll(PDO::FETCH_ASSOC);
return $all_items;
}
}
And on a separate page, to use this, I have:
include 'db.php';
$retrieve = new RetrieveItem();
print_r($retrieve->retrieve_item());
Rather than extend the class, how can I access the Database constructor in the cleanest possible way?
Any help or guidance would be much appreciated.
This amended code is still not working:
Argument 1 passed to RetrieveItem::__construct() must be an instance of Database, none given:
class Database {
public $host = '127.0.0.1';
public $username = 'root';
public $password = '';
public $dbname = 'example';
function __construct(){
$this->connect = new PDO("mysql:host=$this->host;dbname=$this->dbname", $this->username, $this->password);
}
}
class RetrieveItem {
private $_db;
public function __construct(Database $database){
$this->_db = $database;
}
public function retrieve_item(){
$query = $this->connect->prepare("SELECT * FROM posts");
$query->execute();
$all_items = $query->fetchAll(PDO::FETCH_ASSOC);
return $all_items;
}
}
In use:
include 'db.php';
$database = new Database();
$retrieve = new RetrieveItem($database);
print_r($retrieve->retrieve_item());
As you said, with plain dependency injection
class RetrieveItem {
private $_db;
public function __construct(Database $db) {
$this->_db = $db;
}
}
For easier use, you can abstract the injection in container, or, at least, has one super class that recieves the injection.
A dependency injection is so easy but it sounds complicated.
$class1 = new firstclass();
$class2 = new secondclass($class1); //This is a dependency injection.
class firstclass{
private $var1;
private $var2;
public function __construct(){
$this->var1 = "hello";
$this->var2 = "world";
}
public function getvar1(){
return $this->var1;
} //imagine a second one like this for var2;
}
class secondclass{
private $fc; //will hold first class object or the dependency.
public function __construct($firstclassobject){
$this->fc = $firstclassobject;
echo $this->fc->getvar1(); //call dependency methods like this.
echo $this->fc->getvar2();
} //echoes helloworld
}
So you pretty much put an object of one class and asign it to a field in your other class.
for your edit
set this line in your database class at the top.
public connect; //add to dbclass
Then do this in your function
public function retrieve_item(){
$connect = $this->db->connect; //added this
$query = $connect->prepare("SELECT * FROM posts"); //changed this
$query->execute();
$all_items = $query->fetchAll(PDO::FETCH_ASSOC);
return $all_items;
}
class Database {
public $host = '127.0.0.1';
public $username = 'root';
public $password = '';
public $dbname = 'example';
public $connect;
function __construct(){
$this->connect = new PDO("mysql:host=$this->host;dbname=$this->dbname", $this->username, $this->password);
}
}
class RetrieveItem {
private $connect;
public function __construct(&$db){
$this -> connect = $db -> connect;
}
function retrieve_item(){
$query = $this->connect->prepare("SELECT * FROM posts");
$query->execute();
$all_items = $query->fetchAll(PDO::FETCH_ASSOC);
return $all_items;
}
}
// Usage
$db = new Database();
$retrieve_item = new RetrieveItem($db);
Here in Retrieve Item we tried to send the database object as a refereneced variable rather sending a copy of its, which happens to be a good way of passing connection to your operable classes

PHP DataMapper pattern: My class needs an instance of PDO, I want to wrap it inside a Db class

here's what I have:
class Entry
{
public $id;
public $name;
public $seoName;
public $timeCreated;
public function someFunction()
{
}
}
class EntryMapper
{
protected $db;
public function __construct(PDO $db)
{
$this->db = $db;
}
public function saveEntry(Entry &$entry)
{
if($entry->id){
$sql = "";
}
else {
$sql = "INSERT INTO tbl_entry (name, seo_name, time_created) VALUES (:name, :seo_name, :time_created)";
$stmt = $this->db->prepare($sql);
$stmt->bindParam("name", $entry->name);
$stmt->bindParam("seo_name", $entry->seoName);
$stmt->bindParam("time_created", $entry->timeCreated);
$stmt->execute();
$entry->id = $this->db->lastInsertId();
}
}
}
Now, here's how I use it in my view file (currently just testing insert command):
$entry = new Entry();
$entry->name = "Some Company LLC";
$entry->seoName = "some-company-llc";
$entry->timeCreated = date("Y-m-d H:i:s");
$entryMapper = new EntryMapper(new PDO("mysql:host=....."));
$entryMapper->saveEntry($entry);
I want to have the $entryMapper line like this:
$entryMapper = new EntryMapper(new Database());
meaning I should have a separate class Database.php where I would establish the connection.
I tried that, but since my class EntryMapper.php needs an instance of PDO directly, i'm getting an error. I have tried Database extend from PDO but that also raises error saying that PDO constructor was not called in EntryMapper
Any thoughts?
EDIT: if you see any signs of code coupling or similar, let me know because I want to learn to code properly. Thank you very much!
You can use Factory pattern and create the PDO object within a function in the Database class.
class Database {
private const connStr = 'mysql:host=.....';
public static function createPDODatabase() {
return new PDO(connStr);
}
}
So you may call your EntryMapper constructor as:
$entryMapper = new EntryMapper(Database::createPDODatabase());
EDIT: If you want to do it by instantiating the Database object, you should call the PDO constructor in the constructor of the Database class.
class Database extends PDO {
public function __construct($dbname='db_name', $server='localhost', $username='db_user', $password='db_password') {
parent::__construct("mysql:host=$server;dbname=$dbname", $username, $password);
parent::setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
}
}
Then you may just instantiate the Database object.
$entryMapper = new EntryMapper(new Database());
This is how I finally solved it (if a better implementation arises, I will for sure recode). It is an implementation of solution under the accepted answer here: Global or Singleton for database connection?
My ConnFactory.php
include('config/config.php');
class ConnFactory
{
private static $factory;
public static function getFactory()
{
if(!self::$factory){
self::$factory = new ConnFactory();
return self::$factory;
}
}
private $db;
public function pdo()
{
if(!$this->db){
$options = array(
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"
);
$this->db = new PDO("mysql:host=".DB_HOST.";port=".DB_PORT.";dbname=".DB_SCHEMA."", DB_USER, DB_PASS, $options);
}
return $this->db;
}
}
Usage in my view/html file (just a test of insert functionalty):
$entry = new Entry();
$entry->name = "Kartonaža ad Gradačac";
$entry->seoName = "kartonaza-ad-gradacac";
$entry->timeCreated = date("Y-m-d H:i:s");
$entryMapper = new EntryMapper(ConnFactory::getFactory()->pdo());
$entryMapper->saveEntry($entry);

Using PDO database class within other PHP classes

I have a database class that uses PDO. Here's a portion example of it:
class db {
private $host;
private $username;
private $password;
private $con;
private $pdo;
public function __construct( $database = "dnname" )
{
$this->host = "localhost";
$this->username = "username";
$this->password = "pass";
$conStr = "host={$this->host};dbname={$database}";
try {
$this->pdo = new PDO( "mysql:$conStr", $this->username, $this->password );
$this->pdo->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
}
catch( PDOException $e ) {
echo "error ". $e->getMessage();
}
}
public function fetch_single_row($sql, $data)
{
if( $data !== null )
$data = array_values( $data ); // Incase it's an associative array
$sel = $this->pdo->prepare( $sql );
$sel->execute( $data );
$sel->setFetchMode( PDO::FETCH_OBJ );
$obj = $sel->fetch();
return $obj;
}
I would like to use this class and its functions (it has more than I've included) inside other classes.
I have tried many many different things, but the only thing that works so far is starting a new instance of db in every new class which I think is bad practice. For instance:
class cms {
function cms(){
$this->db = new db();
}
function is_admin($id) {
if($this->db->fetch_single_row("SELECT id FROM user WHERE id = ? LIMIT 1", array($id))){
return true;
} else {
return false;
}
}
in my index.php, I include these classes and use them:
include("db.class.php");
include("cms.class.php");
$cms = new cms();
if($cms->is_admin($id)){
//code here
}
What is the correct way to accomplish this?
Take a look into the Singleton Design Pattern, it works great for a DB class
I used to use a class like this for quite a while
abstract class DB
{
protected static $instance;
protected $db;
protected static $host = 'host';
protected static $user = 'user';
protected static $pass = 'pass';
protected static $database;
public static function getInstance()
{
if (!isset(self::$instance)) self::$instance = new static();
return self::$instance;
}
protected function __construct()
{
$this->db = new PDO(sprintf('mysql:host=%s;dbname=%s', static::$host, static::$database), static::$user, static::$pass);
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
public static function db()
{
return static::getInstance()->db;
}
public function fetch_single_row($sql, $data)
{
if( $data !== null )
$data = array_values( $data ); // Incase it's an associative array
$sel = self::db()->prepare( $sql );
$sel->execute( $data );
$sel->setFetchMode( PDO::FETCH_OBJ );
$obj = $sel->fetch();
return $obj;
}
}
I would then extend that class for each different database I would need to connect to
class Main extends DB
{
protected static $database = 'db';
}
You can then use the class like this
$db = Main::getInstance();
$obj = $db->fetch_single_row($sql, $data);

PHP calling a method from outside another class

Not sure of OOP syntax to do this...
I'd like to have aclass that calls the mysqli object
class Voovid_DB {
private $host = 'localhost';
private $user = 'blahblah';
private $password = 'blahblah';
private $name = 'blahblah';
public function __contstuct(){
$dbh= new mysqli( $this->host, $this->user, $this->password, $this->name );
return $dbh;
}
//get and set methods for host, user etc... go here
}
now I'd like to access all the mysqli methods like so
$dbconnect = new Voovid_DB();
if ( $result = $dbconnect->query( "SELECT first_name, last_name FROM members WHERE member_id=9" ) ) {
while ( $row = $result->fetch_assoc() ) {
$first_name = ucfirst( $row['first_name'] );
$last_name = ucfirst( $row['last_name'] );
}
} else {
$errors = $dbconnect->error;
}
i'm new to PHP OOP and not sure how to get to the mysqli methods inside the Voovid_DB class
You have to either extend the MySQLi class, or build a proxy around it.
The easiest is probably to extend it:
class Voovid_DB extends MySQLi {
private $host = 'localhost';
private $user = 'blahblah';
private $password = 'blahblah';
private $name = 'blahblah';
public function __construct(){
// call parent (MySQLi) constructor
parent::__construct( $this->host, $this->user, $this->password, $this->name );
}
// no need for other methods, they already are there
}
Notice the extends MySQLi.
Then your second code snipet should work.
Or, build a proxy:
class Voovid_DB {
private $host = 'localhost';
private $user = 'blahblah';
private $password = 'blahblah';
private $name = 'blahblah';
private $dbh;
public function __construct(){
$this->dbh = new MySQLi($this->host, $this->user, $this->password, $this->name);
}
// this will proxy any calls to this class to MySQLi
public function __call($name, $args) {
return call_user_func_array(array($this->dbh,$name), $args);
}
}
You could define a __call method:
public function __call($method, $arguments) {
return call_user_func_array(array($this->dbh, $method), $arguments);
}
__call is invoked if an undefined or inivisible method is called.
you code is correct.
the only thing you have to do is to make sure that you define your functions in Voovid_DB as public.
private or protected methods cannot be accessed from other classes
store your mysqli object in a public field in your class then you can access it like this:
$dbconnect->mysqlField->query
Constructors aren't supposed to return anything. When you say $dbconnect = new Voovid_DB(); you would normally be trying to create a Voovid_DB object, but it looks like you're using it to try an make a mysqli object. Don't make this the constructor and call the function after you create the voovid_db object.
$obj = new voovid_DB();
$dbConnect = $obj->createMysqli();

Categories