PHP Dependency injection and extending from an umbrella class - php

I have a database class that has a series of functions in it, and I was advised the best thing to do to access those functions from within another class is dependency injection. What I want to do is have one main class that has the database dependency "injected" into it and then other classes extend off of this class, such as Users, Posts, Pages etc.
This is the main class that has the database dependency injected into it.
class Main {
protected $database;
public function __construct(Database $db)
{
$this->database = $db;
}
}
$database = new Database($database_host,$database_user,$database_password,$database_name);
$init = new Main($database);
And then this is the Users class I'm trying to extend off of it.
class Users extends Main {
public function login() {
System::redirect('login.php');
}
public function view($username) {
$user = $this->database->findFirst('Users', 'username', $username);
if($user) {
print_r($user);
} else {
echo "User not found!";
}
}
}
But whenever trying to call the view function for the User class, I'm getting this error Fatal error: Using $this when not in object context. This error is in regards to trying to call $this->database in the Users class. I've tried initializing a new user class and passing the database to it instead, but to no avail.

When you use call_user_func_array and you pass it a callable that is composed of a string name for the class and a string name for the method on the class it does a static call: Class::method(). You need to first define an instance and then pass the instance as the first part of the callable as demonstrated below:
class Test
{
function testMethod($param)
{
var_dump(get_class($this));
}
}
// This call fails as it will try and call the method statically
// Below is the akin of Test::testMethod()
// 'this' is not defined when calling a method statically
// call_user_func_array(array('Test', 'testMethod'), array(1));
// Calling with an instantiated instance is akin to $test->testMethod() thus 'this' will refer to your instnace
$test = new Test();
call_user_func_array(array($test, 'testMethod'), array(1));

Related

How to use an instance of a class within another class in PHP 7

I have what seems like a simple query but I have not found an answer elsewhere.
I have 2 classes, one called DB which essentially connects to the database and can then run queries. I instantiate it at the top of the document $db= new DB; and I can then run a series of queries on the database throughout the page.
The issue I am having is that I want to use this instance within another class I have called User.
I know I can either instantiate again but this does not make sense OR pass the instance of DB when instantiating User, for instance $user = new User($db); but considering the $db instance will be used by most classes I am going to create I am thinking there is a better solution to import it into other classes.
I have looked at the global route but I read it is bad practice + I am getting unexpected global error
class User{
global $db;
public function __construct()
{
var_dump($this-> db);
}//end constructor
}//end class
Since your DB client will be instantiated once and then used everywhere else my initial thought was to recommend passing it as a constructor parameter (dependency injection), but since you are not fan of this approach, I would recommend making your DB client a singleton class, which means it can only be instantiated once and any subsequent attempt would return the same instance everywhere.
You can see a detailed response about singleton classes in PHP at Creating the Singleton design pattern in PHP5.
As a quick example, your DB would look like similar to this:
final class DB
{
public static function getInstance()
{
static $inst = null;
if ($inst === null) {
$inst = new self();
}
return $inst;
}
private function __construct()
{
// your code here ...
}
// your code here ...
}
And then, on your User class you would just get the DB class instance:
class User {
// your code here ...
public function doSomething() {
$db = DB::getInstance();
// your code here ...
}
}
PHP does not handle scopes like Javascript does, your $db is undefined.
The scope of a variable is the context within which it is defined. For the most part all PHP variables only have a single scope. This single scope spans included and required files as well […] Within user-defined functions a local function scope is introduced. Any variable used inside a function is by default limited to the local function scope.
Source: http://php.net/manual/en/language.variables.scope.php
This means that there is only a global scope and a function/method scope in PHP. So, either pass the $db instance into the method as a collaborator
class User{
public function __construct() {}
public function getInfo(Database $db) {
$db->query( /* ... */ );
}
}
$user = new User();
$db = new Database();
$user->getInfo($db);
or pass it in the constructor (dependency injection)
class User{
private $db;
public function __construct(Database $db)
{
$this->db = $db;
}
public function getInfo() {
$this->db->query( /* ... */);
}
}
$db = new Database();
$user = new User($db);
$user->getInfo();

Why does this come up as non-object?

I am trying to create a really simple database connection class in PHP, but I ran into a bit of problem.
First off, here's the code I wrote:
class db {
protected $db;
public function __construct() {
$this->db = new PDO('mysql:host=127.0.0.1;dbname=main', 'root', '');
}
}
Whenever I try to call $db from another class that extends my database class, I get an error like this one:
Fatal error: Call to a member function prepare() on a non-object in class.php
I call the database link in the other class like the following example:
class example extends db {
public function challenge_exists($challenge) {
$query = $this->db->prepare('SELECT * FROM challenges WHERE challenge = :challenge');
$query->bindParam(':challenge', $challenge, PDO::PARAM_STR);
$query->execute();
if($query->rowCount() > 0) {
return true;
}
}
}
I have no idea what I have done wrong and would appreciate any suggestions on how to make this work.
To the question, if there is a __construct() method in the child class then you need to call the __construct() of the parent class explicitly, or else it is not executed and $db is never instantiated. Though you don't show the child constructor in your example code it is safe to assume that there is one based on the behavior, so:
class example extends db {
public function __construct() {
parent::__construct();
//more stuff
}
}
If you don't need a constructor in the child class then remove it and the parent constructor will be called when you instantiate an object of the child class.
As for the design, this is bad and you should consider one database object and inject that into objects that need it or similar approach:
class example {
public function __construct($db) {
$this->db = $db;
}
}
$db = new db(); // do this once
$example = new example($db); // inject when needed
You should never extend an applications class from a database class in the first place.
User is not a database. It's a user. They have nothing in common. Database have to be used as a service by a user, not being a parent.
Not to mention that creating a hundred connections from the same script is a very bad idea.
Regarding the "accepted" answer, it's just wrong. As long as there is no constructor in the child class defined (like in the OP), then parent counstructor is used. Welcome to Stack Overfraud.

PHP use a class variable over several files?

I have 2 files.
One is index.php, which checks for a user login
In this file i create a dbManager class which handles a database.
If dbManager can validate login data, i forward the user to lobby.php (via header).
In lobby.php, i create a Manager class which manages the lobby.
I would like to be able to use the Manager class to forward a query to the DB Manager like this:
index.php
$dbManager = new dbManager();
$userid = $dbManager->validateLogin(($_POST["name"], $_POST["pass"];
if ($userid){
$_SESSION["userid"] = $userid;
header("Location: lobby.php");
}
lobby.php
session_start();
if (isset($_SESSION["userid"])){
$manager = new Manager($_SESSION["userid"]);
$manager->getGames();
}
Class dbManager {
things
}
Class Manager {
public userid;
function __construct($id){
$this->userid = $id;
}
function getGames(){
$ret = $dbManager->queryDB($this->userid);
}
}
I am getting the following notices:
Notice: Undefined variable: dbManager in D:\SecureWAMP_Portable\htdocs\projectX\gameManager.php on line 11
and
Fatal error: Call to a member function getGamesForPlayer() on a non-object in D:\SecureWAMP_Portable\htdocs\projectX\gameManager.php on line 11
What am i doing wrong ?
You should define each class in a new file and include those files (good practice is using autoloading). If you only require one object of a class, you should take a look at the Singleton pattern, as you will always get the same class instance.
For that you define a static protected variable which will hold our class instance and use a static public method to return the class instance and if necessary, create a new class instance. We will define the constructor of each class as private, so the static public method has to be used.
If you have arguments you pass to the constructor, define them for the static public method too and pass the arguments to the constructor in the static public method.
The file DBManager.php will define the class DBManager and will use singleton pattern, as this class will handle connections to the database.
Class DBManager {
static protected $instance = NULL
private __construct() {
//Write the code you want to execute when a new class instance is requested
self::$instance = &$this; //Put a reference to this instance in our static variable
}
//Our static public method to retrieve a class instance
static public app() {
if(self::$instance === NULL OR !is_a(self::$instance, 'DBManager')) { //With is_a we are making sure our self::$instance variable holds a class instance of DBManager
$instance = new DBManager();
}
return self::$instance;
}
/* All your other methods... */
}
Our Manager.php will hold the class Manager and to retrieve a class instance of DBManager we will call our static public method app(). It is valid to also make the class Manager singleton, but only if always only one class instance should exist.
Class Manager {
public userid;
function __construct($id){
$this->userid = $id;
}
function getGames() {
$ret = DBManager::app()->queryDB($this->userid);
}
}
Now our basic index.php just instead of using new DBManager() we will use the static method to return a class instance.
require_once 'DBManager.php';
$dbManager = DBManager::app();
$userid = $dbManager->validateLogin($_POST['name'], $_POST['pass']);
if($userid) {
$_SESSION['userid'] = $userid;
header("Location: lobby.php");
}
In our lobby.php we will use the new Manager class for the first time.
session_start();
require_once 'DBManager.php';
require_once 'Manager.php';
$dbManager = DBManager::app();
if(isset($_SESSION['userid'])) {
$manager = new Manager($_SESSION['userid']);
$manager->getGames();
}
you should not use global vars because it is bad style and will make your life really hard when the application gets bigger.
In your case you want to use the dbManager in different classes.
Since db transaction are likely to be used often, look at the singleton pattern, so there can be only one instance of this class.
But be aware that you should keep the number of singletons as low as possible.
Look into this page to see how the singleton pattern can be implemented in PHP:
http://www.phptherightway.com/pages/Design-Patterns.html
You seem to have a misconception about how php code is distributed and used over multiple files and how these files are used.
For example if you use include() or require() instead of header(), you can refer to the data you have defined so far.
A more advanced approach is to use __autoload() or [spl_autoload_register()](http://php.net/manual/de/function.spl-autoload-register.php) to load classes into your script and have an index.php that starts your script.
(Also read the link #blacksheep_2011 provided)
A good practice is to declare each class in separate file. After that, you need to include the needed class into a file in which you are planning to use that class functionality.
Create file DBManager.php:
Class DBManager {
things
}
Create file Manager.php which will contain Manager class declaration:
include('DBManager.php');
Class Manager {
public userid;
function __construct($id){
$this->userid = $id;
}
function getGames(){
$dbManager = new DBManager();
$ret = $dbManager->queryDB($this->userid);
}
}
Include your classes where they are needed:
index.php
require_once 'DBManager.php';
$dbManager = new DBManager();
$userid = $dbManager->validateLogin(($_POST["name"], $_POST["pass"];
if ($userid){
$_SESSION["userid"] = $userid;
header("Location: lobby.php");
}
lobby.php
session_start();
require_once 'Manager.php';
if (isset($_SESSION["userid"])){
$manager = new Manager($_SESSION["userid"]);
$manager->getGames();
}

PHP Object being passed to class, not being accepted

I have a database class that has a series of functions in it, and I have a Main class that has the dependencies injected into it with other classes such as Users, Posts, Pages etc extending off of it.
This is the main class that has the database dependency injected into it.
class Main {
protected $database;
public function __construct(Database $db)
{
$this->database = $db;
}
}
$database = new Database($database_host, $database_user, $database_password, $database_name);
$init = new Main($database);
And then, this is the Users class I'm extending off of it.
class Users extends Main {
public function login() {
System::redirect('login.php');
}
public function view($username) {
$user = $this->database->findFirst('Users', 'username', $username);
if($user) {
print_r($user);
} else {
echo "User not found!";
}
}
}
But, whenever trying to call the view function for the User class, I'm getting this error Catchable fatal error: Argument 1 passed to Main::__construct() must be an instance of Database, none given. And, if I remove the Database keyword from the _construct parameters, I get this error instead Warning: Missing argument 1 for Main::_construct().
If I pass a variable to the User class from main class it works, but not if I'm trying to pass the Database object, I just can't work out why.
The User class is instantiated via a router with no parameters passed to it.
You can make the $database variable in the Main class static. Then you need to initialize it only once:
class Main {
static $database = null;
public function __construct($db = null)
{
if (self::$database === null && $db instanceof Database) {
self::$database = $db;
}
}
}
The class User extends Main and therefore it inherits the __construct function of the Main class.
You can't instantiate the User class without passing its dependency, the Database instance.
$database = new Database($database_host, $database_user, $database_password, $database_name);
$user = new User($database);
However, you can do it like matewka hinted:
class Main {
static $database = null;
public function __construct($db = null)
{
if (self::$database === null && $db instanceof Database) {
self::$database = $db;
}
}
}
class User extends Main{
function test()
{
return parent::$database->test();
}
}
class Database{
function test()
{
return "DATABASE_TEST";
}
}
$db = new Database();
$main = new Main($db);
$user = new User();
var_dump($user->test());
As clearly noted in the other answers/comments you are not passing the expected dependency into your Users class to the __construct() method it inherits from Main.
In other words, Users expects to be instantiated in the same manner as Main; i.e:
$database = new Database();
$users = new Users($database);
However, I would like to add some detail because constructors in PHP are a little different to other methods when you are defining inheritance relationships between classes, and I think it's worth knowing.
Using inheritance, you can of course override a parent's method in an inheriting class, with a specific caveat in PHP: if you don't match the argument signature of the overridden method you trigger a E_STRICT warning. This is not a showstopper but it's best avoided when endeavouring to write stable robust code:
E_STRICT Enable to have PHP suggest changes to your code which will ensure the best interoperability and forward compatibility of your code. Source
An example:
class Dependency
{
// implement
}
class Foo
{
function doSomething(Dependency $dep)
{
// parent implementation
}
}
class Bar extends Foo
{
function doSomething()
{
// overridden implementation
}
}
$bar = new Bar();
$bar->doSomething();
If you run this code or lint it on the command line with php -l you'll get something like:
PHP Strict standards: Declaration of Bar::doSomething() should be compatible with Foo::doSomething(Dependency $dep) in cons.php on line 22
The reason why I am pointing this out is because this does not apply to __construct:
Unlike with other methods, PHP will not generate an E_STRICT level error message when __construct() is overridden with different parameters than the parent __construct() method has. Source (#Example 1)
So you can safely override __construct in an inheriting class and define a different argument signature.
This example is completely valid:
class Foo
{
function __construct(Dependency $dep)
{
// parent constructor
}
}
class Bar extends Foo
{
function __construct()
{
// overridden constructor
}
}
$bar = new Bar();
Of course, this is dangerous because in the highly likely event inheriting class Bar calls code that relies on Dependency you're going to get an error because you never instantiated or passed the dependency.
So you really should ensure the Dependency in the case above is passed to the parent class. So you are right back where you started, passing the dependency in the constructor, or doing the following, using the parent keyword to call the parent's __construct method:
class Bar extends Main
{
public function __construct()
{
// call the Main constructor and ensure
// its expected dependencies are satisfied
parent::__construct(new Dependency());
}
}
However, best practice dictates that you should pass dependencies into your object instead of instantiating them in your classes, a.k.a dependency injection. So we arrive back at the original solution provided by the other answers :)
$database = new Database();
$users = new Users($database);
Allowing overriding of __construct with different method signatures simply allows greater flexibility when defining classes - you might have am inheriting class that requires more information to be instantiated. For a (ridiculously contrived) example:
class Person
{
public function __construct($name)
{
// implement
}
}
class Prisoner extends Person
{
public function __construct($name, $number)
{
// implement
}
}
$p1 = new Person('Darragh');
$p2 = new Prisoner('Darragh', 'I AM NOT A NUMBER!');
I am pointing this out because __construct is actually quite flexible, if you find yourself in a situation where you want to instantiate an inheriting class with different arguments to the parent, you can, but with the big caveat that you must somehow ensure that you satisfy all expected dependencies of the parent class.
Hope this helps :)

Php access a Global Class

this has been driving me nuts (Drinking Obscene amounts of Coffee and working all night doesn't help) I want to gain access to a class from wherever I am within the application. I instantiate the Class within my index page (which auto loads my lib/classes)But it seems I cannot gain global access to it. This is my index page:
function __autoload($class)
{
require LIBS . $class .'.php';
}
$Core = new Core($server, $user, $pass, $db);
This auto load my Lib/classes perfectly and then I instantiate my Core (This is auto loaded within my Lib/core.php)
Then within my Core is where I create the usual, a database connection, get and check the URL and where I instantiate a few classes (Which are auto loaded) I create a __construct and this is where I want to instantiate a Template class. I wish to have global access for accessing the class within any of my controllers and models.
class Core {
function __construct(DATABASE VARIABLES IN HERE)
{
$this->Template = new Template();
}
}
Ok so I thought I could access the Template Object by doing the following within my parent model and parent controller:
class Controller
{
public $Core;
function __construct()
{
global $Core;
$this->Core = &$Core;
}
}
The Controller is a parent extends all my controllers, therefore I assumed I could just write $this->Core->Template->get_data(); to access the a Template Method? This Seems to throw an error.
Im sure it must be something simple that I have overlooked, if anyone can give me a hand that would be great. This problem is driving me crazy.
Also a side note within my child controllers within my __construct I construct the Parent parent::_construct();
The Error seems to be Notice: Trying to get property of non-object and Fatal error: Call to a member function get_data() on a non-object.
class Controller
{
public $Core;
function __construct(Core $core)
{
$this->Core = $core;
}
}
class ControllerChild extends Controller {
function __construct(Core $core, $someOtherStuff){
parent::__construct($core) ;
//And your $this->Core will be inherited, because it has public access
}
}
note: You dont have to use & sign when working with objects. Objects are automatically passed by reference.
You could make Core a singleton and implement a static function to receive a pointer to the object.
define ('USER', 'username');
define ('PASS', 'password');
define ('DSN', 'dsn');
class Core {
private static $hInstance;
public static function getInstance() {
if (!(self::$hInstance instanceof Core)) {
self::$hInstance = new Core(USER, PASS, DSN);
}
return self::$hInstance;
}
public function __construct($user, $pass, $dsn) {
echo 'constructed';
}
}
Then within your Controller you can use:
$core = Core::getInstance();
Which should output constructed.
Edit
Updated to demonstrate how to construct via the static function w/ output.

Categories