Lif*_*ery 2 php oop model-view-controller design-patterns
为了掌握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();
?>
Run Code Online (Sandbox Code Playgroud)
看看我的代码,我觉得我完全忽略了这一点.
控制器不提供实际值,我也可以直接实例化Model类.
模型类是否应该被实例化?控制器内部还是外部?或者这完全是不好的做法?我如何改进这种结构以支持MVC范例?
您的问题是您在图层中放置了不应该存在的信息.您的数据访问层不需要知道它将如何连接,他们只需要知道有一个正确配置的驱动程序来从中获取数据.
以同样的方式,您将为您的控制器提供他们不需要的职责:创建模型.
为什么这很糟糕?
想象一下,由于某种原因,您可以更改模型类的依赖关系.就像您现在必须同时将模型中的数据存储在两个不同的数据库中以实现冗余一样.
你会怎么做?更改所有控制器并更改实例化其中的模型的方式?
这是很多工作!并容易出错.
那么,我该如何解决这个问题呢?
我喜欢使用依赖注入原理和工厂模式来做到这一点.
为何选择工厂?
将我的模型的创建与使用它的人分开.我的默认模型工厂创建只需要一个数据库连接即可工作的模型:
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");
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在,对于需要存储的模型类,您可以创建如下结构:
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
}
}
Run Code Online (Sandbox Code Playgroud)
所以,如果你有以下课程:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
有了这个,我们用模型中的耦合解决了我们的问题.好.
因此,有了这一切,我如何才能获得准备好从我的控制器存储数据的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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果您只需要一个模型来处理整个应用程序,那么您就可以开始使用了.但你有几个,那么你会有问题.
如果我不想再使用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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在,要完成所有这些工作,您必须在引导程序/前端控制器上进行一些设置:
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);
Run Code Online (Sandbox Code Playgroud)
现在,如果我想将存储引擎更改为纯文本文件,该怎么办?
$defaultStorage = new PlainFileStorage('/path/to/file'); // just this
Run Code Online (Sandbox Code Playgroud)
现在,如果我想将我的存储引擎更改为一个具有2个不同数据库以保存相同数据的自定义实现,该怎么办?
$master = new PdoStorage(new \PDO(...));
$slave = new PdoStorage(new \PDO(.......));
$defaultStorage = new RedundancyStorage($master, $slave);
Run Code Online (Sandbox Code Playgroud)
看到?现在,您存储信息的方式与您的模型无关.
同样,如果你有一些疯狂的业务逻辑改变了你的模型基于设置做事的方式,你也可以改变你的模型工厂:
$defaultModelFactory = new SomeCrazyModelFactory(...);
Run Code Online (Sandbox Code Playgroud)
您的控制器甚至不知道您的模型发生了变化(当然,您必须尊重相同的界面才能互换).
这是一种可能的方式,它或多或少都是我做的,但还有其他一些可能性.