是否应该从页面本身抽象数据库查询?


10

当用PHP编写页面生成时,我经常发现自己写了一些乱七八糟的数据库查询文件。例如,我可能有一个查询来直接从数据库中获取有关帖子的一些数据以显示在页面上,如下所示:

$statement = $db->prepare('SELECT * FROM posts WHERE id=:id');
$statement->bindValue(':id', $id, PDO::PARAM_INT);
$statement->execute();
$post = $statement->fetch(PDO::FETCH_ASSOC);
$content = $post['content']
// do something with the content

这些快速的一次性查询通常很小,但是有时我最终会遇到很大一部分数据库交互代码,这些代码看起来看起来很混乱。

在某些情况下,我通过创建一个简单的函数库来处理与帖子相关的数据库查询来解决此问题,并将该代码块简化为简单的代码:

$content = post_get_content($id);

那太好了。或者至少直到我需要做其他事情为止。也许我需要获取五个最新帖子才能显示在列表中。好吧,我总是可以添加另一个功能:

$recent_posts = post_get_recent(5);
foreach ($recent_posts as $post) { ... }

但这最终会使用SELECT *查询,无论如何我通常通常并不需要它,但是它通常太复杂而无法合理地抽象。最终,我将获得针对每个用例的庞大的数据库交互功能库,或者在每个页面的代码中包含一系列混乱的查询。即使建立了这些库,我也会发现自己需要做一个以前从未使用过的小联接,而且突然我需要编写另一个高度专业化的函数来完成这项工作。

当然,我可以将这些功能用于一般用例和特定交互的查询,但是,一旦我开始编写原始查询,我便开始回落到直接访问所有内容的位置。要么这样做,要么我会变得懒惰,并且将开始在PHP循环中执行操作,无论如何,它们实际上应该直接在MySQL查询中直接完成。

我想问一下那些具有编写Internet应用程序的经验的人:可维护性的提高是否值得额外的代码行以及抽象可能带来的低效率?还是简单地使用直接查询字符串是处理数据库交互的可接受方法?


也许您可以使用存储过程来“包装”凌乱的selects-si只需用所需的某些参数调用此类过程
k102 2012年

Answers:


7

如果您有太多专门的查询功能,则可以尝试将它们分解为可组合的位。例如

$posts = posts()->joinWithComments()->orderBy("post.post_date")->first(5);

还有一些抽象级别的层次结构,您可能会记住这些层次结构。你有

  1. mysql API
  2. 您的 mysql函数,例如select(“ select * from from where foo = bar”); 或更可取的是select("posts")->where("foo = bar")->first(5)
  3. 例如,特定于您的应用程序域的功能 posts()->joinWithComments()
  4. 特定于特定页面的功能,例如 commentsToBeReviewed($currentUser)

遵循这种抽象顺序,在易于维护方面付出了很多代价。页面脚本应仅使用4级功能,应根据3级功能编写4级功能,依此类推。的确,这需要花费更多的时间,但这将使您的维护成本随着时间的推移而保持不变(而不是“哦,天哪,他们希望再改变一次!”)


2
+1这种语法实质上是创建您自己的ORM。如果您真的担心数据库访问变得越来越复杂,并且不想花很多时间修改细节,我建议您使用已经弄清楚这些东西的成熟的Web框架(例如CodeIgniter)。或者至少尝试使用它来看看它给您带来了什么样的语法糖,类似于xpmatteo演示的内容。
哈特利·布罗迪

5

关注分离是值得一读的原理,请参阅有关它的Wikipedia文章。

http://en.wikipedia.org/wiki/Separation_of_concerns

值得一读的另一个原理是耦合:

http://en.wikipedia.org/wiki/Coupling_(computer_science

您有两个明显的问题,一个是数据库中数据的封送处理,另一个是数据的呈现。在一个非常简单的应用程序中,您几乎不必担心,您已经将数据库访问和管理层与呈现层紧密结合在一起,但是对于小型应用程序而言,这没什么大不了的。问题是Web应用程序趋向于发展,如果您想以某种方式(例如性能或功能)扩展Web应用程序,则会遇到一些问题。

假设您正在生成一个由用户生成的评论的网页。随之而来的是尖顶头发的老板,并要求您开始支持Native Apps,例如iPhone / Android等。我们需要一些JSON输出,现在您需要提取生成HTML的渲染代码。完成此操作后,您现在拥有了带有两个渲染引擎的数据访问库,并且一切正常,您可以进行功能扩展。您甚至可以设法将所有内容(即业务逻辑与渲染)分开。

随之而来的是老板,并告诉您他有一个客户想要在其网站上显示帖子,他们需要XML,并且他们每秒需要约5000个请求才能达到最佳性能。现在,您需要生成XML / JSON / HTML。您可以像以前一样再次分离出渲染。但是,您现在需要添加100台服务器以舒适地获得所需的性能。现在,您的数据库受到来自100台服务器的攻击,每台服务器可能有数十个连接,每个服务器都直接暴露给具有不同要求和不同查询等的三个不同应用程序。每台前端计算机上具有数据库访问权限会带来安全隐患,并且这种威胁正在不断增加一个,但我不会去那里。现在您需要扩展性能,每个应用程序都有不同的缓存要求,即不同的关注点。您可以尝试在一个紧密耦合的层(即数据库访问/业务逻辑/渲染层)中进行管理。现在,各层的关注点开始相互影响,即数据库中数据的缓存要求可能与呈现层完全不同,业务层中的逻辑很可能会渗入到SQL即向后移动,否则可能会渗入渲染层,这是我将所有内容都放在一个层中时遇到的最大问题之一,这就像将钢筋混凝土倒入您的应用程序中,而不是一种好方法。

有解决这些类型问题的标准方法,即Web服务的HTTP缓存(squid / yts等)。Web服务本身内部的应用程序级缓存,例如memcached / redis。当您开始扩展数据库时,也将遇到问题,即多个读取主机和一个主服务器,或跨主机的分片数据。您不希望100个主机管理与数据库的各种连接,这些主机根据写或读请求而不同,或者在分片数据库中,如果用户“ usera”对所有写请求都散列到“ [table / database] foo”中,则该主机不可用。

关注点分离是您的朋友,选择何时何地进行分离是一项体系结构决策,同时也是一门艺术。避免紧密耦合任何会演变为完全不同需求的内容。还有很多其他原因使事情分开,即简化了测试,变更的部署,安全性,重用性,灵活性等。


我知道您来自哪里,我也不同意您所说的话,但这只是一个小问题。有问题的项目是一个私人项目,而我当前模型的大部分问题都来自程序员的本能以避免紧密耦合,但是我确实是复杂服务器端开发的新手,因此该项目的结尾有点在我头上。不过,即使我可能在本项目中可能并未完全遵循+1,但在我看来+1还是个不错的建议。
亚历克西斯·金

如果您正在做的事情将保持很小,那么我将使其尽可能简单。YAGNI是一个很好的原则。
哈里

1

我假设当您说“页面本身”时,是指动态生成HTML的PHP​​源文件。

不要查询数据库并在同一源文件中生成HTML。

查询数据库的源文件不是“页面”,即使它是PHP源文件。

在动态创建HTML的PHP​​源文件中,您只需调用访问数据库的PHP源文件中定义的函数即可。


0

我在大多数中型项目中使用的模式如下:

  • 所有SQL查询都与服务器端代码分开放置在单独的位置。

    使用C#,这意味着使用部分类,即将查询放在单独的文件中,前提是可以从单个类访问这些查询(请参见下面的代码)。

  • 这些SQL查询是常量。这很重要,因为它避免了在运行中构建SQL查询的诱惑(因此增加了SQL注入的风险,同时也使得以后更难查看查询)。

C#中的当前方法

例:

// Demo.cs
public partial class Demo : DataRepository
{
    public IEnumerable<Stuff> LoadStuff(int categoryId)
    {
        return this
            .Query(Queries.LoadStuff)
            .With(new { CategoryId = categoryId })
            .ReadRows<Stuff>();
    }

    // Other methods go here.
}

public partial class Demo
{
    private static class Queries
    {
        public const string LoadStuff = @"
select top 100 [StuffId], [SomeText]
    from [Schema].[Table]
    where [CategoryId] = @CategoryId
    order by [CreationUtcTime]";

        // Other queries go here.
    }
}

这种方法的好处是使查询位于单独的文件中。这使DBA可以审阅和修改/优化查询,并将修改后的文件提交给源代码管理,而不会与开发人员所做的提交相冲突。

一个相关的好处是,可以以某种方式配置源代码控制,以将DBA的访问限制为仅包含查询的那些文件,并拒绝对其余代码的访问。

可以在PHP中执行吗?

PHP既缺少部分类又缺少内部类,因此就不能在PHP中实现。

您可以使用DemoQueries包含常量的单独静态类()创建单独的文件,前提是该类可以从任何地方访问。此外,为了避免污染全局范围,可以将所有查询类放在专用的名称空间中。这将创建一个相当冗长的语法,但我怀疑您是否可以避免冗长:

namespace Data {
    public class Demo inherit DataRepository {
        public function LoadStuff($categoryId) {
            $query = \Queries\Demo::$LoadStuff;
            // Do the stuff with the query.
        }

        // Other methods go here.
    }
}

namespace Queries {
    public static class Demo {
        public const $LoadStuff = '...';

        // Other queries go here.
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.