【问题标题】:Should a model object instantiate inside a controller class?模型对象是否应该在控制器类中实例化?
【发布时间】:2014-02-09 01:24:28
【问题描述】:

为了掌握 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 类。

是否应该实例化模型类?控制器内部还是外部?还是完全是不好的做法? 我将如何改进此结构以支持 MVC 范例?

【问题讨论】:

  • new 用于工厂,并且代码量非常有限。您希望避免在业务逻辑中调用 newthis talk takes +/- 40 minutes, but is well worth your time.
  • 你应该在你的控制器中注入一个工厂。在this post 中可能会稍微解释一下“如何制作”。 “模型”也是一个层,而不是一个类。更多信息:here.
  • @tereško 在实践中,如果我添加了一个工厂来实例化我的类,我会将这个类放在哪里?听起来好像它不属于模型和控制器目录
  • @LifeQuery 工厂本身并不是 MVC 模式的一部分。我倾向于将此类结构放在/lib/common/ 中。特别是因为它们通常是可重复使用的。

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


【解决方案1】:

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

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

为什么这样不好?

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

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

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

那么,我该如何解决呢?

我喜欢使用依赖注入原则工厂模式来做到这一点。

为什么是工厂?

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

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

这样,我们解决了模型内的耦合问题。好的。

那么,有了这一切,我怎样才能得到一个模型组件,准备好从我的控制器存储我的数据?

直接:

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

您的控制器甚至不知道您的模型已更改(当然,您必须尊重相同的接口才能被允许互换执行此操作)。

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

【讨论】:

  • 感谢您花时间写下所有这些。我仍在尝试找出这个谜题的所有细节。
  • 我知道这听起来有点复杂,但本质是在你的对象中放置越来越少的信息,让所有(或几乎)信息都来自“外部世界”。通过这种方式,您将获得灵活性。
【解决方案2】:

您在解释模型时混淆了关注点。在模型-视图-控制器中,模型是某种类型的知识,它可以是代表领域概念的单个类或更复杂的结构,例如CMS 上下文中的页面或文档。

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

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

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

【讨论】:

  • 为什么数据库连接是跨 MVC 层提供的服务?我认为视图应该与数据库无关,处理数据库相关职责不是严格模型的工作吗?
  • 在很多 php web 框架中都是这种情况,因为模型对象往往是active records。当您不使用活动记录时,您有某种服务为您提供 CRUD 功能,通常由控制器调用。大多数对 MVC 的解释都是“控制器操作模型”,这并不意味着模型具有数据库连接。
猜你喜欢
  • 2013-02-11
  • 2023-03-20
  • 2020-11-27
  • 2011-07-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-20
  • 2013-01-16
相关资源
最近更新 更多