15

我有以下示例,其中我倾向于使用几个类来创建一个简单的 Web 应用程序。

文件层次结构看起来像这样。

> cupid 
    - libs 
        - request
        - router 
        - database
        - view 
    - bootstrap.php 
  - index.php 

index.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 
        ...

并填充blog.php属性映射表行,以及 getter 和 setter ..

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 包装器结合起来以在我的引导文件中获取博客?” ..

4

3 回答 3

14

就域对象而言,您基本上已经编写了一个,即您的博客对象。要成为一个领域模型,一个类必须提供一个表示以及问题空间中概念的任何功能。

这里更有趣的问题以及您似乎正在努力解决的问题是如何持久化域模型。遵循单一职责原则的原则,您的 Blog 类应该处理作为博客文章并做博客文章可以做的事情,而不是存储一个。为此,您将引入博客文章存储库的概念,该存储库将处理存储和检索这种类型的对象。下面是如何做到这一点的简单实现。

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

要真正成为 SOLID,上述类应该是 BlogRepository 接口的数据库特定实现,以遵守 IoC。还应该为 BlogRepository 提供一个工厂,以根据从存储中检索到的数据实际创建博客对象。

在我看来,这样做的一大好处是您可以在一个地方实现和维护与数据库相关的所有博客交互。

此方法的其他优点

  • 为您的域对象实现缓存将是微不足道的
  • 切换到不同的数据源(从平面文件、博客 api、文档数据库服务器、PostgresSQL 等)可以轻松完成。

您也可以使用类型感知 ORM 来获得针对同一问题的更通用的解决方案。基本上,这个 Repository 类只不过是单个类的 ORM。

这里重要的是你不是直接与数据库对话,而是让 sql 分散在你的代码中。这会造成维护噩梦,并将您的代码与数据库的模式耦合。

于 2015-07-17T19:43:19.593 回答
4

就我个人而言,我总是倾向于将数据库操作粘在数据库类中,该类完成初始化类、打开连接等所有繁重的工作。它还有通用查询包装器,我将包含普通占位符的 SQL 语句传递给它绑定变量,加上要绑定的变量数组(或参数的可变数量方法,如果这更适合您)。如果您想单独绑定每个参数而不使用$stmt->execute(array());您只需在您选择的数据结构中传递具有值的类型,多暗数组,字典,JSON,任何适合您的需要并且您发现易于使用的东西。

它自己的模型类(在您的情况下为博客)然后对数据库进行子类化。然后你有几个选择要做。您想使用构造函数只创建新对象吗?您是否希望它仅基于 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, ingress, body),save()和 aload(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 = new Blog(123);加载特定的博客,或者$blog = new Blog(); $blog->title = "title"; ... $blog->save();如果你想要一个新的。

于 2015-07-24T08:40:23.363 回答
3

“我怎样才能将博客模型与 PDO 包装器结合在一起以在我的引导文件中获取博客?”..

要将两者联系在一起,您可以使用对象关系映射器 (ORM)。ORM 库仅用于将 PHP 类粘合到数据库行。周围有几个用于 PHP 的 ORM 库。此外,大多数 ORM 都具有内置的数据库抽象层,这意味着您可以轻松切换数据库供应商而无需任何麻烦。

使用 ORM 时的注意事项:
虽然引入 ORM 也会带来一些臃肿(和一些学习),但将时间花在简单的单个Blog对象上可能不值得。虽然,如果您的博客条目也有作者、一个或多个类别和/或相关文件,ORM 可能很快会帮助您读取/写入数据库。从您发布的代码来看,ORM 将在未来扩展应用程序时得到回报。


更新:使用教义 2 的示例

您可以查看官方 Doctrine 文档的查询部分,以查看您具有读取访问权限的不同选项。重新考虑您给出的示例:

// current implementation    
$article = $database->select("SELECT blog, content FROM foo WHERE id = ?",[$id]);

// possible implementation using Doctrine
$article = $em->getRepository(Blog::class)->find($id);

但是,理想情况下,您定义自己的存储库以将业务逻辑与 Doctrines API 分开,如下例所示:

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

我希望该示例说明您可以轻松地将业务领域模型和逻辑与底层库分离,以及 ORM 可以如何发挥作用。

于 2015-07-26T17:03:32.457 回答