Search code examples
phpoopmodel-view-controllerobserver-pattern

OO, MVC and Observer pattern not working as expected


In my class, we made a simple application using MVC with the observer pattern in Java and it works. The view cannot call any methods from the model that are not included in the (Observable) interface and vice versa.

I am quite a fan of PHP and decided to make the same (simplified) example in PHP. I noticed that even though I am using an interface and passing the reference of the model as an interface, the view can still call every method inside the model, rendering the entire pattern useless.

Is there something I overlooked or is this not possible in PHP?

The PHP code (every reference, method, etc is the exact same as in the Java application) :

class App
{
    public function __construct()
    {
        $model = new Model();
        $controller = new Controller($model);
    }

}

class Model implements Observable
{
    private $view;
    private $count = 1;

    public function __construct()
    {
        echo 'Model created. <br>';
    }

    public function registrate(Observer $view)
    {
        $this->view = $view;
        echo 'Model: view is registered. <br>';
    }

    public function addOne()
    {
        $this->count += 1;
        $this->view->modelChanged($this);
    }

    public function getCounter()
    {
        return $this->count;
    }

    public function getMessage()
    {
        return 'The view should not be able to call this method.';
    }

}

class Controller
{
    private $view;
    private $model;

    public function __construct(Model $model)
    {
        echo 'Controller created. <br>';
        $this->model = $model;
        $this->view = new View($this->model);
        $this->model->addOne();
    }

}

class View implements Observer
{
    public function __construct(Observable $model)
    {
        echo 'View created. <br>';
        $model->registrate($this);
    }

    public function modelChanged(Observable $model)
    {
        // Should only be able to call method "getCounter()"
        echo $model->getMessage();
    }

}

interface Observable
{
    public function registrate(Observer $view);
    public function getCounter();
}

interface Observer
{
    public function modelChanged(Observable $model);
}

The output, if you run this is:

Model created.

Controller created.

View created.

Model: view is registered.

The view should not be able to call this method. As you can see, the view can call a method of the model that is not declared inside the Observable interface.

How is this possible and why does this not work in PHP like it does in Java?


Solution

  • Well of course the view can call every method you've defined on the model: All the methods are public, which means they're callable from anywhere. Just define them as protected or private instead...

    Of course, that'd limit the ways in which you can use the model in other components (such as the controller). To get around that problem, a simple fix would be to create a wrapper, which you can wrap around the model when you pass it to the view:

    class View implements Observable
    {
        public function __construct(ViewObservable $model)
        {
            //do stuff here
        }
    }
    //Wrapper:
    class ViewObservable
    {
        /**
         * @var Model
         */
        protected $payload = null;
        public class __construct(Observable $model)
        {
            $this->payload = $model;
        }
        public function getCounter()
        {
            return $this->payload->getCounter();
        }
    }
    

    But really, you might want to rethink a thing or 2. It's good to use interfaces, but it doesn't make a lot of sense (to me at least) to have all components in an MVC architecture implement the same interface. All components have different jobs to perform, and therefore should have different interface requirements.