nealarec/laravel-json-query-builder

Laravel JSON查询构建器

v2.4.3 2024-09-26 03:29 UTC

This package is auto-updated.

Last update: 2024-09-26 03:38:59 UTC


README

Laravel JSON查询构建器

此包允许根据以下特殊逻辑构建从JSON对象开始的查询。

安装

通过composer安装此包。它将自动注册为Laravel服务提供者。

composer require asseco-voice/laravel-json-query-builder

使用

为了使用此包,您需要实例化JsonQuery()并提供两个依赖项。一个是Illuminate\Database\Eloquent\Builder实例,另一个是JSON/数组输入。

一旦实例化,您需要运行search()方法,查询将在提供的构建器对象上构建。

$jsonQuery = new JsonQuery($builder, $input);
$jsonQuery->search();

此包的开发命名规范

  • 参数是顶级JSON键名(见下方的选项以下
  • 参数值是顶级JSON内的值。
  • 参数是一个单个键值对。
  • 单个参数进一步拆分为列 / 操作符 / 值
{
    "search": {                         <-- parameter
        "first_name": "=foo",           <-- argument
        "last_name": "  =       bar  "  <-- argument
         ˆˆˆˆˆˆˆˆˆ      ˆ       ˆˆˆ
          column    operator   value
    }
}

参数分解

参数遵循特殊的逻辑以查询数据库。可以使用以下JSON参数(键)

  • search - 将执行查询逻辑(见下文详细说明以下
  • returns - 只返回提供的列。
  • order_by - 将根据提供的值对结果进行排序。
  • group_by - 将根据提供的值对结果进行分组。
  • relations - 将加载给定模型的关联。
  • limit - 将限制返回的结果。
  • offset - 将从给定点开始返回结果子集。此参数必须与limit参数一起使用。
  • count - 将返回记录计数。
  • soft_deleted - 将包括软删除的模型在搜索结果中。
  • doesnt_have_relations - 将只返回没有指定任何关联的条目。

搜索

逻辑以"列": "操作符值"的形式执行,我们假设以下内容

  • 代表数据库中的一列。多个键可以用新的JSON键值对分隔。
  • 可以使用.作为分隔符来搜索相关模型,例如"relation.column": "操作符值"。**注意**这将执行一个WHERE EXISTS,它不会在包含在关联中时过滤结果关联。要执行WHERE NOT EXISTS,可以使用!relation.column
  • 操作符是可用于查询的主要操作符之一(见下文以下列出)
  • 是分号(;)分隔的值列表(例如,"列": "=value;value2;value3"),它们也可以有微操作符(例如,"列": "=value;!value2;%value3%")。

主要操作符

  • = - 等于
  • != - 不等于
  • < - 小于(需要确切一个值)
  • > - 大于(需要确切一个值)
  • <= - 小于等于(需要确切一个值)
  • >= - 大于等于(需要确切一个值)
  • <> - 之间(需要确切两个值)
  • !<> - 不在之间(需要确切两个值)

示例

{
  "search": {
    "first_name": "=foo1;foo2",
    "last_name": "!=bar1;bar2"
  }
}

将执行一个 SELECT * FROM some_table WHERE first_name IN ('foo1', 'foo2') AND last_name NOT IN ('bar1', 'bar2') 查询。

如果你对一个字符串类型的列传递单个值,则将使用 LIKE 操作符而不是 IN。对于 Postgres 数据库,使用 ILIKE 而不是 LIKE 以支持不区分大小写的搜索。

{
  "search": {
    "first_name": "=foo",
    "last_name": "!=bar"
  }
}

将执行一个 SELECT * FROM some_table WHERE first_name LIKE 'foo' AND last_name NOT LIKE 'bar' 查询。

微操作符

  • ! - 取反值。仅在值的开始处起作用(即 !value)。
  • % - 执行 LIKE 查询。仅在值的开始、结束或两端起作用(即 %valuevalue%%value%)。
  • 逻辑操作符用于使用多个操作符(你不能对单个列进行 =1||2,但可以 =1||=2)进行操作(顺序很重要!)
    • && 允许你使用 AND 连接值
    • || 允许你使用 OR 连接值
{
  "search": {
    "first_name": "=!foo",
    "last_name": "=bar%"
  }
}

将执行一个 SELECT * FROM some_table WHERE first_name NOT LIKE 'foo' AND last_name LIKE 'bar%' 查询。

注意,在这里 !value 的行为与主操作符 != 相同。区别在于 != 主操作符取反值列表,而 !value 仅取反特定值。即 !=value1;value2 在语义上等同于 =!value1;!value2

逻辑操作符示例

{
  "search": {
    "first_name": "=foo||=bar"
  }
}

将执行 SELECT * FROM some_table WHERE first_name IN ('foo') OR first_name IN ('bar') 查询。

注意,逻辑操作符使用标准的 bool 逻辑优先级,因此 x AND y OR z AND q 等同于 (x AND y) OR (z AND q)

嵌套关系搜索

如果使用的键是关系名称,则可以嵌套另一个搜索对象,这将执行 whereHas() 查询构建器方法。

{
    "search": {
        "some_relation": {
            "search": { ... }
        },
    }
}

返回

使用 returns 键将仅返回其中指定的字段。此操作符接受值数组或单个值。

示例

返回单个值

{
  "returns": "first_name"
}

将执行一个 SELECT first_name FROM ... 查询。

返回多个值

{
  "returns": ["first_name", "last_name"]
}

将执行一个 SELECT first_name, last_name FROM ... 查询。

聚合和 SQL 函数支持

现在你可以以这种方式使用聚合和某些 SQL 函数,如 sumavgmonthyear,即 ave:year:column_name,你还可以在分组查询中使用 count:distinct:name

返回聚合

{
  "returns": ["year:date", "month:date", "sum:value"],
  "group_by": ["year_date", "month_date"]
}

将执行 SELECT year(date) as year_date, month(date) as month_date, sum(value) FROM ... GROUP BY year_date, month_date 查询。

你也可以根据需要嵌套函数,例如

{
  "returns": ["min:year:date"]
}

将执行 SELECT min(year(date)) as min_year_date FROM ... 查询。

你可以在 SQLFunctionsDatabaseFunctions 上了解更多信息。

排序

使用 order_by 键根据给定的键执行 'order by'。键的顺序很重要!

参数假定采用 "column": "direction" 格式,其中 direction 必须是 asc(升序)或 desc(降序)。如果只提供列,则假定方向为升序。

示例

{
  "order_by": {
    "first_name": "asc",
    "last_name": "desc"
  }
}

将执行一个 SELECT ... ORDER BY first_name asc, last_name desc 查询。

分组

使用 group_by 键根据给定的键执行 'group by'。键的顺序很重要!

参数假定是单个属性或属性数组。

由于分组的行为类似于普通 SQL 查询,请确保选择正确的字段和聚合函数。

示例

{
  "group_by": ["last_name", "first_name"]
}

将执行一个 SELECT ... GROUP BY last_name, first_name

关系

可以使用 relations 参数加载对象关系。此操作符接受一个值数组或单个值。

简单

示例

解析单个关系

{
  "relations": "containers"
}

解析多个关系

{
  "relations": ["containers", "addresses"]
}

如果正确定义并遵循Laravel约定,关系应该是可预测的

  • 1:1 & 多对多 - 关系名称为复数(例如,Contact 有多个 Addresses,因此关系名称为 'addresses')
  • 多对1 - 关系名称为单数(例如,Comment 属于一个 Post,因此关系名称为 'post')
  • 重要:由于Laravel以 snake_case 形式返回API响应,因此可以提供 snake_case 关系(尽管 camelCase 也同样有效)用于多词关系。例如,执行 "relations": "workspace_items" 与调用 "relations": "workspaceItems" 等效,但建议使用 snake_case 方法。

可以使用点符号递归地加载关系

{
  "relations": "media.type"
}

这将同时加载媒体关系并立即解析媒体类型。如果您需要解析多个二级关系,可以提供一个包含这些关系的数组

{
  "relations": ["media.type", "media.category"]
}

这将加载包含每个媒体对象解析类型和类别的媒体关系

也可以使用点符号无限制地堆叠关系。但必须注意的是,这可能会 严重影响性能

{
  "relations": "media.type.contact.title"
}

复杂

关系也可以是一个对象,其中包含嵌套的搜索属性,以进一步过滤给定的结果集。

示例

{
  "relations": [
    {
      "media": {
        "search": {
          "media_type_id": "=1"
        }
      }
    }
  ]
}

将在对象上加载一个 media 关系,但只返回 media_type_id 等于 1 的对象。

限制

您可以通过执行以下操作来限制获取的结果数量

{
  "limit": 10
}

这将执行 SELECT * FROM table LIMIT 10

偏移量

您可以使用偏移量进一步限制返回的结果,但需要同时使用限制。

{
  "limit": 10,
  "offset": 5
}

这将执行 SELECT * FROM table LIMIT 10 OFFSET 5

计数

您可以通过添加 count 键来获取记录的计数而不是具体的记录

{
  "count": true
}

这将执行 SELECT count(*) FROM table

软删除

默认情况下,软删除的记录会被排除在搜索之外。这可以通过 soft_deleted 来覆盖。

{
  "soft_deleted": true
}

没有关系

如果您只想找到没有指定关系的条目,可以使用 doesnt_have_relations 键。

{
  "doesnt_have_relations": "containers"
}

如果您想指定多个关系,可以按以下方式操作

{
  "doesnt_have_relations": ["containers", "addresses"]
}

顶级逻辑运算符

此外,还可以通过顶级逻辑运算符对搜索子句进行分组。

可用运算符

  • && AND
  • || OR

如果没有顶级运算符,则假定 AND 运算符。

示例

这些运算符接受单个对象,或对象数组,有一些值得注意的差异。单个对象将在给定属性上应用运算符

{
  "search": {
    "&&": {
      "id": "=1",
      "name": "=foo"
    }
  }
}

结果为 id=1 AND name=foo。而对象数组将在数组对象之间应用运算符,而不是在对象内部

{
  "search": {
    "||": [
      {
        "id": "=1",
        "name": "=foo"
      },
      {
        "id": "=2",
        "name": "=bar"
      }
    ]
  }
}

结果为 (id=1 AND name=foo) OR (id=2 AND name=bar)。这是故意为之的,默认运算符是 AND,因此它将在对象内部应用。

如果您想将内部属性更改为 OR,可以递归地进行操作

{
  "search": {
    "||": [
      {
        "||": {
          "id": "=1",
          "name": "=foo"
        }
      },
      {
        "id": "=2",
        "name": "=bar"
      }
    ]
  }
}

结果为 (id=1 OR name=foo) OR (id=2 AND name=bar)

荒谬示例

由于逻辑是递归的,您可以像您想要的那样荒谬和深入,但在这个阶段,可能更明智的是重新审视您实际上想要从生活中和宇宙中得到的什么

{
  "search": {
    "||": {
      "&&": [
        {
          "||": [
            {
              "id": "=2||=3",
              "name": "=foo"
            },
            {
              "id": "=1",
              "name": "=foo%&&=%bar"
            }
          ]
        },
        {
          "we": "=cool"
        }
      ],
      "love": "<3",
      "recursion": "=rrr"
    }
  }
}

分解

  • 步骤 1
{
    "id": "=2||=3",
    "name": "=foo"
},

结果: (id=2 OR id=3) AND name=foo

  • 步骤 2
{
  "id": "=1",
  "name": "=foo%&&=%bar"
}

结果: id=1 AND (name LIKE foo% AND name LIKE %bar)

  • 步骤 3(合并)
"||": [
    {...},
    {...}
]

结果: (step1) OR (step2)

  • 步骤 4 we=cool
{
  "we": "=cool"
}
  • 步骤 5(合并)
"&&": [
    {
        "||": [...]
    },
    {
        "we": "=cool"
    }
],

结果:(步骤3) AND (步骤4)

  • 步骤6(最终合并)
"||": {
    "&&": [...],
    "love": "<3",
    "recursion": "=rrr"
}

结果:(步骤5) OR love<3 OR recursion=rrr

最终查询(用火消灭它)

(((id=2 OR id=3) AND name=foo) OR (id=1 AND (name LIKE foo% AND name LIKE %bar))) AND we=cool) OR love<3 OR recursion=rrr

配置

除了标准的查询字符串搜索外,还可以提供额外的包配置。

通过运行 php artisan vendor:publish --provider="Asseco\JsonQueryBuilder\JsonQueryServiceProvider" 来发布配置。

配置文件中的所有键都有每个键上方详细的说明。

包扩展

配置发布后,您将看到几个键,您可以用自定义代码来扩展它们。

  • 请求参数注册在 request_parameters 配置键下。您可以通过添加自己的自定义参数来扩展此功能。它需要扩展 Asseco\JsonQueryBuilder\RequestParameters\AbstractParameter 才能工作。
  • 运算符注册在 operators 配置键下。可以通过添加一个扩展 Asseco\JsonQueryBuilder\SearchCallbacks\AbstractCallback 的类来扩展这些。
  • 类型注册在 types 配置键下。可以通过添加一个扩展 Asseco\JsonQueryBuilder\Types\AbstractType 的类来扩展这些。