Php 在以下项目中实现S.O.L.I.D域对象模型
在下面的例子中,我倾向于使用几个类来创建一个简单的web应用程序 文件层次结构如下所示Php 在以下项目中实现S.O.L.I.D域对象模型,php,oop,design-patterns,model,domain-object,Php,Oop,Design Patterns,Model,Domain Object,在下面的例子中,我倾向于使用几个类来创建一个简单的web应用程序 文件层次结构如下所示 > cupid - libs - request - router - database - view - bootstrap.php - index.php index.php只调用bootstrap.php,而bootstrap.php又包含如下内容: // bootstrap.php namespac
> cupid
- libs
- request
- router
- database
- view
- bootstrap.php
- index.php
index.php
只调用bootstrap.php
,而bootstrap.php又包含如下内容:
// bootstrap.php
namespace cupid
use request, router, database, view;
spl_autoload_register(function($class){ /* autoload */ });
$request = new view;
$response = new response;
$router = new router;
$database = new database;
$router->get('/blog/{id}', function($id) use ($database, $view) {
$article = $database->select("SELECT blog, content FROM foo WHERE id = ?",[$id]);
$view->layout('blogPage', ['article'=>$article]);
});
正如你可能知道的,我的问题是这一行:
$article = $database->select("SELECT blog, content FROM foo WHERE id = ?", [$id]);
我不想使用它,而是尝试“域对象模型”方法
现在,我将添加另一个名为domain的文件夹,其中包含blog.php
> cupid
- domain
- Blog.php
- libs
...
并使用属性映射表行、getter和setter填充blog.php
namespace App\Domain;
class Blog {
private $id, $title, $content, $author;
public function getTitle(){
return $this->title;
}
public function setTitle($title){
$this->title = $title;
}
...
}
我的问题是:假设我对DOM的理解到目前为止是正确的,并且我有一个CRUD/ORM类或PDO包装器来查询数据库
“我如何将博客模型与PDO包装器绑定在一起,以便在我的引导文件中获取博客?”
对于域对象,您基本上已经编写了一个,即您的博客对象。要成为一个领域模型,一个类必须提供一个表示以及问题空间中概念的任何功能 这里更有趣的问题是如何持久化域模型,而您似乎正在努力解决这个问题。按照单一责任原则的原则,你的博客类应该处理成为一篇博客文章,做博客文章可以做的事情,而不是存储一篇。为此,您将引入博客文章存储库的概念,该存储库将处理存储和检索这种类型的对象。下面是如何实现这一点的简单实现
class BlogRepository {
public function __construct(\cupid\database $db){
$this->db = $db;
}
public function findById($id){
$blogData = $this->db->select("select * from blog where id = ?", [$id]);
if ($blogData){
return $this->createBlogFromArray($blogData);
}
return null;
}
public function findAllByTag($tag){...}
public function save(Blog $blog) {...}
private function createBlogFromArray(array $array){
$blog = new Blog();
$blog->setId($blogData["id"]);
$blog->setTitle($blogData["title"]);
$blog->setContent($blogData["content"]);
$blog->setAuthor($blogData["author"]);
return $blog;
}
}
那么你的控制器应该是这样的
$router->get('/blog/{id}', function($id) use ($blogRepository, $view) {
$article = $blogRepository->findById($id);
if ($article) {
$view->layout('blogPage', ['article'=>$article]);
} else {
$view->setError("404");
}
});
为了真正可靠,上面的类应该是BlogRepository接口的特定于数据库的实现,以遵守IoC。可能还应该向BlogRepository提供一个工厂,以便根据从存储中检索的数据实际创建blog对象
在我看来,这样做的最大好处之一是您可以在一个地方实现和维护与数据库的所有博客相关交互
此方法的其他优点
- 为域对象实现缓存将是微不足道的
- 切换到不同的数据源(从平面文件、blogger api、文档数据库服务器、PostgresSQL等)很容易
这里重要的一点是,您没有直接与数据库对话,而是将sql分散在代码中。这将创建一个维护噩梦,并将代码与数据库的模式相耦合 就我个人而言,我总是倾向于将数据库操作放在一个数据库类中,该类完成初始化类、打开连接等所有繁重工作。它还具有通用查询包装器,我将SQL语句传递到该包装器,其中包含绑定变量的普通占位符,以及要绑定的变量数组(或者,如果适合的话,可以使用可变数量的参数)。如果您希望单独绑定每个参数,而不使用
$stmt->execute(array());
您只需在您选择的数据结构中传递类型和值,多维数组、字典、JSON,任何适合您需要的类型都可以轻松使用
然后,模型类it self(在您的例子中是Blog)将数据库子类化。然后您有一些选择。是否要使用构造函数仅创建新对象?是否希望它仅基于ID加载?还是两者的混合?例如:
function __construct(id = null, title = null, ingress = null, body = null) {
if(id){
$row = $this->getRow("SELECT * FROM blog WHERE id = :id",id); // Get a single row from the result
$this->title = $row->title;
$this->ingress = $row->ingress;
$this->body = $row->body;
... etc
} else if(!empty(title,ingress,body)){
$this->title = title;
... etc
}
}
可能两者都没有?如果您喜欢,您可以跳过构造函数并使用new(title,incress,body)
、save()
和load(id)
方法
当然,如果您只需配置一些类成员,并让数据库超类根据您发送或设置为成员变量的内容进行查询构建,则查询部分可以进一步泛化。例如:
class Database {
$columns = []; // Array for storing the column names, could also be a dictionary that also stores the values
$idcolumn = "id"; // Generic id column name typically used, can be overridden in subclass
...
// Function for loading the object in a generic way based on configured data
function load($id){
if(!$this->db) $this->connect(); // Make sure we are connected
$query = "SELECT "; // Init the query to base string
foreach($this->columns as $column){
if($query !== "SELECT ") $query .= ", "; // See if we need comma before column name
$query .= $column; // Add column name to query
}
$query .= " FROM " . $this->tablename . " WHERE " . $this->idcolumn . " = :" . $this->idcolumn . ";";
$arg = ["col"=>$this->idcolumn,"value"=>$id,"type"=>PDO::PARAM_INT];
$row = $this->getRow($query,[$arg]); // Do the query and get the row pass in the type of the variable along with the variable, in this case an integer based ID
foreach($row as $column => $value){
$this->$column = $value; // Assign the values from $row to $this
}
}
...
function getRow($query,$args){
$statement = $this->query($query,$args); // Use the main generic query to return the result as a PDOStatement
$result = $statement->fetch(); // Get the first row
return $result;
}
...
function query($query,$args){
...
$stmt = $this->db->prepare($query);
foreach($args as $arg){
$stmt->bindParam(":".$arg["col"],$arg["value"],$arg["type"]);
}
$stmt->execute();
return $stmt;
}
...
}
现在,正如您看到的load($id)
,getrow($query,$args)
和query($query,$args)
是完全通用的。''getrow()'只是query()上的包装器
在获取第一行时,您可能需要使用多个不同的包装器,以不同的方式解释语句结果。如果无法将特定于对象的包装器设置为泛型,您甚至可能需要将其添加到模型中。现在,在您的案例中,Blog
中的模型可以如下所示:
class Blog extends Database {
$title;
$ingress;
$body;
...
function __construct($id = null){
$this->columns = ["title","ingress","body","id",...];
$this->idcolumn = "articleid"; // override parent id name
...
if($id) $this->load($id);
}
...
}
按如下方式使用它:$blog=newblog(123);
加载特定的博客,或者$blog=newblog();$blog->title=“title”…$blog->save();
如果您想要新的博客
“我如何将博客模型与PDO包装器绑定在一起,以便在我的引导文件中获取博客?”
为了将两者结合在一起,您可以使用对象关系映射器(ORM)。ORM库的构建只是为了将PHP类粘贴到数据库行上。有几个库。此外,大多数ORM都有内置的数据库抽象层,这意味着您可以轻松地切换数据库供应商
使用ORM时的注意事项:在引入ORM的同时,也引入了一些膨胀(和一些学习),可能不值得为一个
博客
对象花费时间。但是,如果您的博客条目也有作者、一个或多个类别和/或相关文件,ORM可能很快会帮助您阅读/编写数据库。从您发布的代码判断,ORM在将来扩展应用程序时会有所回报
更新:使用条令2的示例 你可以看看官方的教义文件
// current implementation
$article = $database->select("SELECT blog, content FROM foo WHERE id = ?",[$id]);
// possible implementation using Doctrine
$article = $em->getRepository(Blog::class)->find($id);
use Doctrine\ORM\EntityRepository;
interface BlogRepositoryInterface {
public function findById($id);
public function findByAuthor($author);
}
class BlogRepsitory implements BlogRepositoryInterface {
/** @var EntityRepository */
private $repo;
public function __construct(EntityRepository $repo) {
$this->repo = $repo;
}
public function findById($id) {
return $this->repo->find($id);
}
public function findByAuthor($author) {
return $this->repo->findBy(['author' => $author]);
}
}