4

I'm working on a small MVC framework in PHP for an exercise. PHP, however, doesn't seem to like my Controller class. The class contains an instance of a loader that loads views:

abstract class Controller
{
    public $load;
    function __construct($load) 
    {
        $this->load = $load;
    }
    abstract public function index();
}

From there, I can override Controller for all my controllers. For instace, my index controller:

class Index extends Controller
{
    public function index()
    {
        $this->load->view("hello_world");
    }
}

But when I create it:

require 'Controller.php';
require 'Load.php'
require 'controllers/Index.php';
$i = new Index(new Load());
$i->index();

I get this error:

PHP Fatal error:  Call to a member function view() on a non-object in /var/www/controllers/Index.php on line 7

Can you guys help me out? I know I set the load in the constructor, and the load class does have a method called view, so why is it giving me this error? Also: Load class, just for good measure

class Load
{
    public function view($filename, $data = null)
    {
        if(is_array($data)) extract($data);
        include ROOT.DS.'views'.DS.$filename.'.php';
    }
}
tereško
  • 58,060
  • 25
  • 98
  • 150
DelishusCake
  • 107
  • 1
  • 5
  • the `__construct` method of `Load` needs to return the object. but you need to show the constructors too, or we can just guess. – dan-lee Jun 17 '12 at 17:55
  • @DanLee Constructores don't need to return objects. – Niko Jun 17 '12 at 17:59
  • @Niko: Of course they don't *need* too, but I thought Index seems to expect an object – dan-lee Jun 17 '12 at 18:00
  • @DanLee Let me clarify what I meant: There is absolutely no point in returning something from a constructor, because `new` ignores the return value anyway. – Niko Jun 17 '12 at 18:11
  • @Niko And I mean constructs like `new A(new B());` then the constructor of `B` (a factory maybe?) of course can return an object and inject it into `A` – dan-lee Jun 17 '12 at 19:47
  • @DanLee I'd be interested to hear about how you do that! http://codepad.viper-7.com/lvN2rK – Niko Jun 17 '12 at 20:56
  • That's what I meant, how isn't that value not returned? – dan-lee Jun 17 '12 at 21:35
  • @DanLee The constructor of class B returns an instance of Z, but `$b` is still an instance of B (as the output proves). Following your logic, `$b` should be the returned instance of Z. – Niko Jun 17 '12 at 23:34

2 Answers2

8

The problem is with this code, and it's not always obvious:

class Index extends Controller
      ^^^^^
{
    public function index()
                    ^^^^^
    {
        $this->load->view("hello_world");
    }
}

This is the same name and therefore a PHP 4 backwards compatible constructor. The parent's constructor then is not called, $load not set and the function not defined.

Knowing this, there are many solutions, including:

namespace DelishusCake;

Introduce a Namespace

This automatically fixes your issue. You need to place this on top of the file.

class Index extends Controller
{
    public function index($load = NULL)
    {
        isset($load) && $this->load = $load;
        $this->load->view("hello_world");
    }
}

Make the PHP4 backwards compatible constructor work

Or:

class MyIndex extends Controller
{
    public function index()
    {            
        $this->load->view("hello_world");
    }
}

Rename the class

Or:

class Index extends Controller
{
    public function __construct($load) {
        parent::__construct($load);
    }
    public function index()
    {            
        $this->load->view("hello_world");
    }
}

Add a PHP 5 constructor, call the parent's constructor

Keep in mind that you only need this because it's the same name. The in depth description you can find as well in the PHP Manual on the Constructors and Destructors page.

hakre
  • 193,403
  • 52
  • 435
  • 836
  • Thank you so much! Your explanation was really helpful! I changed the controller name and it worked fine. – DelishusCake Jun 17 '12 at 18:10
  • 1
    Yeah, I think that's the best solution (next to namespacing if PHP 5.3 is ok) because you don't introduce any more code you would need to change in multiple places as change happens. – hakre Jun 17 '12 at 18:11
2

You need to instantiate the parent class.

class Index extends Controller
{
    public function __construct($load) {
      parent::__construct($load);
    }

    public function index() {
      $this->load->view("hello_world");
    }
}
flowfree
  • 16,356
  • 12
  • 52
  • 76
  • 1
    Will cure, but is not the right explanation. Index === controller. – hakre Jun 17 '12 at 17:56
  • Unlike in languages such as Java or C++, constructors are inherited from the parent class - that's the whole point with naming them "__construct()" since PHP 5.0 – Niko Jun 17 '12 at 18:01
  • Thanks, that fixes the problem, but I thought constructors were inherited? – DelishusCake Jun 17 '12 at 18:02
  • @DelishusCake: Yes they are, but only if the child class does not override it. As explained in my answer, the `Index::index()` function is a constructor and overrides the parent constructor. And that's the whole story. – hakre Jun 17 '12 at 18:07