ablshd
ablshd

Reputation: 15

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.

Upvotes: 0

Views: 97

Answers (3)

Darragh Enright
Darragh Enright

Reputation: 14136

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 :)

Upvotes: 0

enterx
enterx

Reputation: 879

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());

Upvotes: 0

matewka
matewka

Reputation: 10148

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;
        }
    }
}

Upvotes: 1

Related Questions