在以下项目中实现S.O.L.I.D域对象模型

时间:2015-07-17 17:43:59

标签: php oop design-patterns model domain-object

我有以下示例,其中我倾向于使用几个类来创建一个简单的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]); 

你可以说,我的问题是这一行:

blog.php

我不想使用,而是尝试“域对象模型”方法。

现在,我将添加另一个名为domain的文件夹,其中包含> 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; } ... }

{{1}}

我的问题是:假设我对DOM的理解到目前为止是正确的,并且我有一个CRUD / ORM类或PDO包装器来查询数据库;

  

“我如何联系在一起,即博客模型与PDO包装器在我的引导程序文件中获取博客?” ..

3 个答案:

答案 0 :(得分: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分散在整个代码中。这会造成维护噩梦,并将您的代码与数据库的架构相结合。

答案 1 :(得分:4)

就我个人而言,我总是倾向于将数据库操作放在数据库类中,这会完成初始化类,打开连接等所有繁重的工作。它还具有通用的查询包装器,我传递的SQL语句包含绑定变量的正常占位符,以及要绑定的变量数组(或者如果更适合您,参数的可变数量接近)。如果你想单独绑定每个param而不使用$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()load(id)方法。

当然,如果您只是配置一些类成员并让Database-superclass根据您发送的内容或设置为成员变量来执行查询构建,则可以进一步概括查询部分。例如:

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();如果您想要新博客。

答案 2 :(得分:3)

  

“我如何联系在一起,即博客模型与PDO包装器在我的引导程序文件中获取博客?”..

要将两者结合在一起,您可以使用对象关系映射器(ORM)。 ORM库只是为了将PHP类粘贴到数据库行而构建的。周围有几个ORM libraries for PHP。此外,大多数ORM都有内置的数据库抽象层,这意味着您可以毫不费力地切换数据库供应商。

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

更新:使用Doctrine 2的示例

您可以查看官方Doctrine文档的querying section,了解您对读取权限的不同选项。重新考虑你给出的例子:

// 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可以发挥多大作用。