Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/oop/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Php 如何创建流畅的查询界面?_Php_Oop_Fluent Interface - Fatal编程技术网

Php 如何创建流畅的查询界面?

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)完成该语句,然后执行它并返回结果,对吗 但是,事实并非如此,因为

我知道如何链接类方法(使用“return$this”等),但我尝试的是以一种智能的方式链接它们,请看一下:

$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');
    
    如果你不想打这个额外的电话怎么办?在这种情况下,class
    DatabaseTable
    也必须实现
    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'));