Php 如何创建流畅的查询界面?
我知道如何链接类方法(使用“return$this”等),但我尝试的是以一种智能的方式链接它们,请看一下:Php 如何创建流畅的查询界面?,php,oop,fluent-interface,Php,Oop,Fluent Interface,我知道如何链接类方法(使用“return$this”等),但我尝试的是以一种智能的方式链接它们,请看一下: $albums = $db->select('albums')->where('x', '>', '20')->limit(2)->order('desc'); 从这个代码示例中我可以理解的是,前3个方法(select、where、limit)构建了将要执行的查询语句,最后一个方法(order)完成该语句,然后执行它并返回结果,对吗 但是,事实并非如此,因为
$albums = $db->select('albums')->where('x', '>', '20')->limit(2)->order('desc');
从这个代码示例中我可以理解的是,前3个方法(select、where、limit)构建了将要执行的查询语句,最后一个方法(order)完成该语句,然后执行它并返回结果,对吗
但是,事实并非如此,因为我可以轻松地删除这些方法中的任何一种(当然“选择”除外),或者——更重要的是——更改它们的顺序,这样就不会出错!!这意味着“选择”方法可以处理工作,对吗?那么,在已经调用方法“select”之后,其他3个方法如何添加/影响查询语句 这需要一个非常优雅的解决方案 与其重新发明轮子,不如研究现有的框架
我建议拉威尔使用雄辩的ORM。您将能够做到这一点,甚至可以做更多。您可能需要一种方法来启动实际的查询,而像
select
和order\u by
这样的方法只存储到该点的信息
但是,如果实现该接口,并在第一次命中revend
或current
时运行查询(想想foreach
),或者通过调用对象的count()。我个人不希望使用像这样构建的库,我更希望有一个显式调用,这样我就可以看到触发查询的位置。如何实现可组合查询:10k英尺视图
不难意识到,为了实现这一点,被链接的方法必须增量地建立一些数据结构,这些数据结构最终由执行最终查询的某个方法进行解释。但对于如何协调这一点,有一定的自由度
示例代码是
$albums = $db->select('albums')->where('x', '>', '20')->limit(2)->order('desc');
我们在这里看到了什么
有一种类型,$db
是它的一个实例,它至少公开了一个select
方法。请注意,如果希望能够对调用进行完全重新排序,则此类型需要公开具有可参与调用链的所有可能签名的方法
每个链式方法都返回一个实例,该实例公开了所有相关签名的方法;这可能与$db
的类型相同,也可能不同
在收集“查询计划”之后,我们需要调用一些方法来实际执行它并返回结果(我将称之为实现查询的过程)。由于明显的原因,此方法只能是调用链中的最后一个方法,但在本例中,最后一个方法是order
,这似乎不正确:我们希望能够在调用链中将其移动得更早。让我们记住这一点
因此,我们可以将发生的事情分解为三个不同的步骤
第一步:开始
我们确定至少需要一种类型来收集有关查询计划的信息。让我们假设类型如下所示:
interface QueryPlanInterface
{
public function select(...);
public function limit(...);
// etc
}
class QueryPlan implements QueryPlanInterface
{
private $variable_that_points_to_data_store;
private $variables_to_hold_query_description;
public function select(...)
{
$this->encodeSelectInformation(...);
return $this;
}
// and so on for the rest of the methods; all of them return $this
}
QueryPlan
需要适当的属性,不仅要记住它应该生成什么查询,还要记住将该查询定向到何处,因为它是在调用链末尾您手头上的这种类型的实例;这两条信息都是实现查询所必需的。我还提供了一个queryplan接口
类型;其意义将在稍后阐明
这是否意味着$db
属于QueryPlan
类型?乍一看,你可能会说是的,但经过仔细检查,这样的安排开始出现问题。最大的问题是过时状态:
// What would this code do?
$db->limit(2);
// ...a little later...
$albums = $db->select('albums');
这将检索多少张专辑?因为我们没有“重置”查询计划,所以它应该是2。但从最后一行来看,这一点并不明显,这一行读起来非常不同。这是一个糟糕的安排,可能会导致不必要的错误
那么如何解决这个问题呢?一个选项是让select
重置查询计划,但这会遇到相反的问题:$db->limit(1)->select('albums')
现在选择所有相册。这看起来不太好
选项是通过安排第一次调用返回新的QueryPlan
实例来“启动”链。这样,每个链都在一个单独的查询计划上运行,虽然您可以一点一点地编写查询计划,但您再也不能意外地编写了。所以你可以:
class DatabaseTable
{
public function query()
{
return new QueryPlan(...); // pass in data store-related information
}
}
这解决了所有这些问题,但要求您始终在前面编写->query()
:
$db->query()->limit(1)->select('albums');
如果你不想打这个额外的电话怎么办?在这种情况下,classDatabaseTable
也必须实现QueryPlanInterface
,不同之处在于每次实现都将创建一个新的QueryPlan
:
class DatabaseTable implements QueryPlanInterface
{
public function select(...)
{
$q = new QueryPlan();
return $q->select(...);
}
public function limit(...)
{
$q = new QueryPlan();
return $q->limit(...);
}
// and so on for the rest of the methods
}
您现在可以毫无问题地编写$db->limit(1)->选择('albums')
;这种安排可以描述为“每次编写$db->something(…)
时,您就开始编写一个独立于所有以前和将来的查询的新查询”
步骤2:链接
这实际上是最简单的部分;我们已经看到QueryPlan
中的方法如何始终返回$this
以启用链接
步骤3:具体化
我们仍然需要一些方式来说“好吧,我已经写完了;给我结果”。完全可以为此目的使用专用方法:
interface QueryPlanInterface
{
// ...other methods as above...
public function get(); // this executes the query and returns the results
}
这使您能够编写
$anAlbum = $db->limit(1)->select('albums')->get();
这个解决方案没有错,也有很多对的地方:很明显,实际的查询是在哪一点执行的。但这个问题使用了一个似乎不那么有效的例子。有可能实现这样的语法吗
答案是肯定的和否定的。是的,因为这确实是可能的,但不是的,因为
$albums = $db->select('albums'); // no materialization yet
foreach ($albums as $album) {
// ...
}
interface QueryPlanInterface extends IteratorAggregate
{
// ...other methods as above...
public function getIterator();
}
$albums = iterator_to_array($db->select('albums'));