您的问题是您将不应该存在的信息放入图层中。您的数据访问层不需要知道它是如何连接的,他们只需要知道有一个正确配置的驱动程序可以从中获取数据。
以同样的方式,您赋予控制器他们不需要的职责:创建模型。
为什么这样不好?
想象一下,由于某种原因,您更改了模型类的依赖关系。就像您现在必须将模型中的数据同时存储在两个不同的数据库中以实现冗余一样。
你会怎么做?更改所有控制器并更改在其中实例化模型的方式?
这是很多工作!并且容易出错。
那么,我该如何解决呢?
我喜欢使用依赖注入原则和工厂模式来做到这一点。
为什么是工厂?
将我的模型的创建与使用它的人分离。我的默认模型工厂创建的模型只需要一个数据库连接即可工作:
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(...);
您的控制器甚至不知道您的模型已更改(当然,您必须尊重相同的接口才能被允许互换执行此操作)。
这是一种可能的方式,或多或少是我的做法,但还有其他一些可能性。