php - 模型对象应该在 Controller 类中实例化吗?

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

?>

看看我的代码,我觉得我完全没有捕获重点。
Controller 没有实际值(value),我还不如直接实例化 Model 类。

是否应该实例化模型类?在 Controller 内部还是外部?或者这完全是一种不好的做法? 我将如何改进这个结构以支持 MVC 范例?

最佳答案

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

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

Why this is bad?

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

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

工作量很大!并且容易出现错误。

So, how can I solve this?

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

Why a factory?

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

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

所以,如果您有以下类(class):

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

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

So, with all this, how can I get a Model component that is ready to store my data from my controller?

直接:

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,而是想将数据存储在纯文本文件中怎么办?

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

Oh wait! We have the freaking factory to create models for us! So, let's use it!

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

现在,为了让所有这些一起工作,您必须在 Bootstrap /前端 Controller 上进行一些设置:

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

Now, what do I do if I want to change my storage engine to plain text files?

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

Now, what do I do if I want to change my storage engine to one custom implementation that have 2 distinct database to save the same data?

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

看到了吗?现在,您存储信息的方式与您的模型无关。

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

$defaultModelFactory = new SomeCrazyModelFactory(...);

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

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

关于php - 模型对象应该在 Controller 类中实例化吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21654037/

相关文章:

java - 无法实现鼠标监听器

java - 如何在所有子构造函数中自动包含父方法的执行?

java - 如何构建我自己的应用程序设置

php - SQL 循环不会打印列名

php - MYSQL - 错误代码 :- 1241 "Operand should contain 1 column(s) "

php - 数据库连接和SELECT

java - android 在哪里放置 OnClickListener OOP 明智的?

php - 具有 GET 变量的 Codeigniter 路由

java - 值被覆盖而不是添加

c# - DI 和存储库模式