模型对象应该在控制器类中实例化吗?

时间:2014-02-09 01:24:29

标签: php oop design-patterns model-view-controller

试着掌握MVC,我写了以下内容:     

//Model/Model.php
class Model
{
    private $dbh;

    public function __construct()
    {
        $this->dbh = dbConnect();
    }

    private function dbConnect()
    {
        //return database connection
    }

    public function crudMethod()
    {
        //interact with databse using $this->dbh
    }

}

//Controller/Controller.php
class Controller
{
    public $modelConnection;

    public function __construct()
    {
        $this->modelConnection = new Model();
    }
}


//View/index.php
$obj = new Controller;
$obj->modelConnection->crudMethod();

?>

看着我的代码,我觉得我完全忽略了这一点。
控制器不提供实际值,我也可以直接实例化Model类。

Model类是否应该被实例化?控制器内部还是外部?或者这完全是不好的做法? 我如何改进这种结构以支持MVC范例?

2 个答案:

答案 0 :(得分:5)

您的问题是您将信息放在您不应该存在的图层中。您的数据访问层不需要知道它将如何连接,他们只需要知道有一个正确配置的驱动程序来从中获取数据。

以同样的方式,您将为您的控制器提供他们不需要的职责:创建模型。

  

为什么这很糟糕?

想象一下,出于某种原因,您可以更改模型类的依赖关系。就像你现在必须同时将模型中的数据存储在两个不同的数据库中以实现冗余一样。

你会怎么做?更改所有控制器并更改实例化其中的模型的方式?

这是很多工作!并容易出错。

  

那么,我该如何解决这个问题?

我喜欢使用依赖注入原则工厂模式来执行此操作。

  

为什么选择工厂?

将我的模型的创建与使用它的人分开。我的默认模型工厂创建的模型只需要一个数据库连接就可以工作:

namespace MyApplication\Model\Factory;

use MyApplication\Storage\StorageInterface;

interface FactoryInterface {
    public function create($modelClassName);
}

class WithStorage implements FactoryInterface {
    const MODEL_NAMESPACE = '\\MyApplication\\Model\\';

    private $storage;

    public function __construct(StorageInterface $storage) {
        $this->storage = $storage;
    }

    public function create($modelClassName) {
        $refl = new \ReflectionClass(self::MODEL_NAMESPACE . $modelClassName);
        try {
            return $refl->newInstance($this->storage);
        } catch (\ReflectionException $e) {
            throw new RuntimeException("Model {$modelClassName} not found");
        }
    }
}

现在,对于需要存储的模型类,您可以创建如下结构:

namespace MyApplication\Storage;

interface StorageInterface {
    public function insert($container, array $data);
    public function update($container, array $data, $condition);
    // etc..
}

class PdoStorage implements StorageInterface {
    private $dbh;
    public function __construct(\PDO $dbh) {
        $this->dbh = $dbh;
    }

    public function insert($container, array $data) {
        // impl. omitted
    }

    public function update($container, array $data, $condition) {
        // impl. omitted
    }
}

所以,如果你有以下课程:

namespace MyApplication\Model;

use MyApplication\Storage\StorageInterface;
// Entity impl. will be omitted for brevity.
use MyApplication\Entity\EntityInterface; 

abstract class AbstractApplicationModel {
    private $containerName;
    private $storage;

    protected function __construct($name, StorageInterface $storage) {
        $this->containerName = (string) $name;
        $this->storage = $storage;
    }

    public function save(EntityInterface $entity) {
        // impl. omitted
    }

    public function delete(EntityInterface $entity) {
        // impl. omitted
    }
}

class UserModel extends AbstractApplicationModel {
    public function __construct(StorageInterface $storage) {
        parent::__construct('users', $storage);
    }
}

有了这个,我们用模型中的耦合解决了我们的问题。确定。

  

所以,有了这一切,我怎样才能获得一个准备好从我的控制器存储数据的Model组件?

直接:

namespace MyApplication\Controller;

use MyApplication\Model\UserModel;
use MyApplication\Storage\PDOStorage;
use MyApplication\Entity\User;

class UserController {
    public function onCreate() {
        $model = new UserModel(new Storage(new \PDO(...))); // Here's our problem
        $entity = User::createFromArray([
            'name' => 'John',
            'surname' => 'Doe',
        ]);

        try {
            $model->save($entity);
        } catch (Exception $e) {
            echo 'Oops, something is wrong: ' . $e;
        }
    }
}

如果您只有一个模型可以处理整个应用程序,那么您已准备好了。但是你有几个,那么你就会有问题。

如果我不想再使用PDO作为我的存储驱动程序并使用MySQLi,该怎么办?如果我不想再使用RDBMS,而是想将我的数据存储在纯文本文件中,该怎么办?

这样,您必须更改所有控制器的实现(违反OCP)。有很多重复的工作要做。我讨厌这个!

  等等哦!我们有一个疯狂的工厂为我们创造模型!所以,让我们使用它!

namespace MyApplication\Controller;

use MyApplication\Model\Factory\FactoryInterface;
use MyApplication\Entity\User;

abstract class AbstractController {
    private $modelFactory;

    public function __construct(ModelFactory $factory) {
        $this->modelFactory = $factory;
    }

    public function getModelFactory() {
        return $this->modelFactory;
    }
}

class UserController {
    public function onCreate() {
        $model = $this->getModelFactory()->create('UserModel'); // now it's better
        $entity = User::createFromArray([
            'name' => 'John',
            'surname' => 'Doe',
        ]);

        try {
            $model->save($entity);
        } catch (Exception $e) {
            echo 'Oops, something is wrong: ' . $e;
        }
    }
}

现在,要完成所有这些工作,您必须在引导程序/前端控制器上进行一些设置:

use MyApplication\Storage\PDOStorage;
use MyApplication\Model\Factory\WithStorage as ModelFactory;

$defaultPDODriver = new \PDO(...); 
$defaultStorage = new PdoStorage($defaultPDODriver);
$defaultModelFactory = new ModelFactory($defaultStorage);

$controller = new UserController($defaultModelFactory);
  

现在,如果我想将存储引擎更改为纯文本文件,该怎么办?

$defaultStorage = new PlainFileStorage('/path/to/file'); // just this
  

现在,如果我想将我的存储引擎更改为一个具有2个不同数据库以保存相同数据的自定义实现,该怎么办?

$master = new PdoStorage(new \PDO(...));
$slave = new PdoStorage(new \PDO(.......));
$defaultStorage = new RedundancyStorage($master, $slave);

请参阅?现在,您存储信息的方式与您的模型无关。

同样,如果您有一些疯狂的业务逻辑可以根据设置改变模型的工作方式,您也可以更改模型工厂:

$defaultModelFactory = new SomeCrazyModelFactory(...);

您的控制器甚至不知道您的模型已更改(当然,您必须尊重相同的界面才能互换)。

这是一种可能的方式,它或多或少都是我做的,但还有其他几种可能性。

答案 1 :(得分:1)

您在解释模型时会混淆考虑因素。在模型 - 视图 - 控制器中,模型是某种类型的知识,它可以是表示域概念或更复杂结构的单个类,例如CMS环境中的页面或文档。

但是在您的示例中,您的Model类实际上只是数据库连接的Singleton持有者,其目的。

数据库连接是跨MVC层提供的服务,最好使用依赖注入进行连接。您不应该在Controller代码中实例化服务。

在控制器中实例化 Model 对象是否有意义在很大程度上取决于上下文,但没有规则禁止它。