getkirby/kql
Composer 安装命令:
composer require getkirby/kql
包简介
Kirby Query Language
README 文档
README
Kirby's Query Language API combines the flexibility of Kirby's data structures, the power of GraphQL and the simplicity of REST.
The Kirby QL API takes POST requests with standard JSON objects and returns highly customized results that fit your application.
Playground
You can play in our KQL sandbox. The sandbox is based on the Kirby starterkit.
ℹ️ Source code of the playground is available on GitHub.
Example
Given a POST request to: /api/query
{
"query": "page('photography').children",
"select": {
"url": true,
"title": true,
"text": "page.text.markdown",
"images": {
"query": "page.images",
"select": {
"url": true
}
}
},
"pagination": {
"limit": 10
}
}
🆗 Response
{
"code": 200,
"result": {
"data": [
{
"url": "https://example.com/photography/trees",
"title": "Trees",
"text": "Lorem <strong>ipsum</strong> …",
"images": [
{
"url": "https://example.com/media/pages/photography/trees/1353177920-1579007734/cheesy-autumn.jpg"
},
{
"url": "https://example.com/media/pages/photography/trees/1940579124-1579007734/last-tree-standing.jpg"
},
{
"url": "https://example.com/media/pages/photography/trees/3506294441-1579007734/monster-trees-in-the-fog.jpg"
}
]
},
{
"url": "https://example.com/photography/sky",
"title": "Sky",
"text": "<h1>Dolor sit amet</h1> …",
"images": [
{
"url": "https://example.com/media/pages/photography/sky/183363500-1579007734/blood-moon.jpg"
},
{
"url": "https://example.com/media/pages/photography/sky/3904851178-1579007734/coconut-milkyway.jpg"
}
]
}
],
"pagination": {
"page": 1,
"pages": 1,
"offset": 0,
"limit": 10,
"total": 2
}
},
"status": "ok"
}
Installation
Manual
Download and copy this repository to /site/plugins/kql of your Kirby installation.
Composer
composer require getkirby/kql
Documentation
API Endpoint
KQL adds a new query API endpoint to your Kirby API (i.e. yoursite.com/api/query). This endpoint requires authentication.
You can switch off authentication in your config at your own risk:
return [ 'kql' => [ 'auth' => false ] ];
Sending POST Requests
You can use any HTTP request library in your language of choice to make regular POST requests to your /api/query endpoint. In this example, we are using the fetch API and JavaScript to retrieve data from our Kirby installation.
const api = "https://yoursite.com/api/query"; const username = "apiuser"; const password = "strong-secret-api-password"; const headers = { Authorization: "Basic " + Buffer.from(`${username}:${password}`).toString("base64"), "Content-Type": "application/json", Accept: "application/json", }; const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "page('notes').children", select: { title: true, text: "page.text.kirbytext", slug: true, date: "page.date.toDate('d.m.Y')", }, }), headers, }); console.log(await response.json());
query
With the query, you can fetch data from anywhere in your Kirby site. You can query fields, pages, files, users, languages, roles and more.
Queries Without Selects
When you don't pass the select option, Kirby will try to come up with the most useful result set for you. This is great for simple queries.
Fetching the Site Title
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "site.title", }), headers, }); console.log(await response.json());
🆗 Response
{ code: 200, result: "Kirby Starterkit", status: "ok" }
Fetching a List of Page IDs
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "site.children", }), headers, }); console.log(await response.json());
🆗 Response
{ code: 200, result: [ "photography", "notes", "about", "error", "home" ], status: "ok" }
Running Field Methods
Queries can even execute field methods.
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "site.title.upper", }), headers, }); console.log(await response.json());
🆗 Response
{ code: 200, result: "KIRBY STARTERKIT", status: "ok" }
select
KQL becomes really powerful by its flexible way to control the result set with the select option.
Select Single Properties and Fields
To include a property or field in your results, list them as an array. Check out our reference for available properties for pages, users, files, etc.
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "site.children", select: ["title", "url"], }), headers, }); console.log(await response.json());
🆗 Response
{ code: 200, result: { data: [ { title: "Photography", url: "/photography" }, { title: "Notes", url: "/notes" }, { title: "About us", url: "/about" }, { title: "Error", url: "/error" }, { title: "Home", url: "/" } ], pagination: { page: 1, pages: 1, offset: 0, limit: 100, total: 5 } }, status: "ok" }
You can also use the object notation and pass true for each key/property you want to include.
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "site.children", select: { title: true, url: true, }, }), headers, }); console.log(await response.json());
🆗 Response
{ code: 200, result: { data: [ { title: "Photography", url: "/photography" }, { title: "Notes", url: "/notes" }, { title: "About us", url: "/about" }, { title: "Error", url: "/error" }, { title: "Home", url: "/" } ], pagination: { ... } }, status: "ok" }
Using Queries for Properties and Fields
Instead of passing true, you can also pass a string query to specify what you want to return for each key in your select object.
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "site.children", select: { title: "page.title", }, }), headers, }); console.log(await response.json());
🆗 Response
{ code: 200, result: { data: [ { title: "Photography", }, { title: "Notes", }, ... ], pagination: { ... } }, status: "ok" }
Executing Field Methods
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "site.children", select: { title: "page.title.upper", }, }), headers, }); console.log(await response.json());
🆗 Response
{ code: 200, result: { data: [ { title: "PHOTOGRAPHY", }, { title: "NOTES", }, ... ], pagination: { ... } }, status: "ok" }
Creating Aliases
String queries are a perfect way to create aliases or return variations of the same field or property multiple times.
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "page('notes').children", select: { title: "page.title", upperCaseTitle: "page.title.upper", lowerCaseTitle: "page.title.lower", guid: "page.id", date: "page.date.toDate('d.m.Y')", timestamp: "page.date.toTimestamp", }, }), headers, }); console.log(await response.json());
🆗 Response
{ code: 200, result: { data: [ { title: "Explore the universe", upperCaseTitle: "EXPLORE THE UNIVERSE", lowerCaseTitle: "explore the universe", guid: "notes/explore-the-universe", date: "21.04.2018", timestamp: 1524316200 }, { ... }, { ... }, ... ], pagination: { ... } }, status: "ok" }
Subqueries
With such string queries you can of course also include nested data
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "page('photography').children", select: { title: "page.title", images: "page.images", }, }), headers, }); console.log(await response.json());
🆗 Response
{ code: 200, result: { data: [ { title: "Trees", images: [ "photography/trees/cheesy-autumn.jpg", "photography/trees/last-tree-standing.jpg", "photography/trees/monster-trees-in-the-fog.jpg", "photography/trees/sharewood-forest.jpg", "photography/trees/stay-in-the-car.jpg" ] }, { ... }, { ... }, ... ], pagination: { ... } }, status: "ok" }
Subqueries With Selects
You can also pass an object with a query and a select option
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "page('photography').children", select: { title: "page.title", images: { query: "page.images", select: { filename: true, }, }, }, }), headers, }); console.log(await response.json());
🆗 Response
{ code: 200, result: { data: [ { title: "Trees", images: { { filename: "cheesy-autumn.jpg" }, { filename: "last-tree-standing.jpg" }, { filename: "monster-trees-in-the-fog.jpg" }, { filename: "sharewood-forest.jpg" }, { filename: "stay-in-the-car.jpg" } } }, { ... }, { ... }, ... ], pagination: { ... } }, status: "ok" }
Pagination
Whenever you query a collection (pages, files, users, roles, languages) you can limit the resultset and also paginate through entries. You've probably already seen the pagination object in the results above. It is included in all results for collections, even if you didn't specify any pagination settings.
limit
You can specify a custom limit with the limit option. The default limit for collections is 100 entries.
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "page('notes').children", pagination: { limit: 5, }, select: { title: "page.title", }, }), headers, }); console.log(await response.json());
🆗 Response
{ code: 200, result: { data: [ { title: "Across the ocean" }, { title: "A night in the forest" }, { title: "In the jungle of Sumatra" }, { title: "Through the desert" }, { title: "Himalaya and back" } ], pagination: { page: 1, pages: 2, offset: 0, limit: 5, total: 7 } }, status: "ok" }
page
You can jump to any page in the resultset with the page option.
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "page('notes').children", pagination: { page: 2, limit: 5, }, select: { title: "page.title", }, }), headers, }); console.log(await response.json());
🆗 Response
{ code: 200, result: { data: [ { title: "Chasing waterfalls" }, { title: "Exploring the universe" } ], pagination: { page: 2, pages: 2, offset: 5, limit: 5, total: 7 } }, status: "ok" }
Pagination in Subqueries
Pagination settings also work for subqueries.
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "page('photography').children", select: { title: "page.title", images: { query: "page.images", pagination: { page: 2, limit: 5, }, select: { filename: true, }, }, }, }), headers, }); console.log(await response.json());
Multiple Queries in a Single Call
With the power of selects and subqueries you can basically query the entire site in a single request
const response = await fetch(api, { method: "post", body: JSON.stringify({ query: "site", select: { title: "site.title", url: "site.url", notes: { query: "page('notes').children.listed", select: { title: true, url: true, date: "page.date.toDate('d.m.Y')", text: "page.text.kirbytext", }, }, photography: { query: "page('photography').children.listed", select: { title: true, images: { query: "page.images", select: { url: true, alt: true, caption: "file.caption.kirbytext", }, }, }, }, about: { text: "page.text.kirbytext", }, }, }), headers, }); console.log(await response.json());
Allowing Methods
KQL is very strict with allowed methods by default. Custom page methods, file methods or model methods are not allowed to make sure you don't miss an important security issue by accident. You can allow additional methods though.
Allow List
The most straight forward way is to define allowed methods in your config.
return [ 'kql' => [ 'methods' => [ 'allowed' => [ 'MyCustomPage::cover' ] ] ] ];
DocBlock Comment
You can also add a comment to your methods' doc blocks to allow them:
class MyCustomPage extends Page { /** * @kql-allowed */ public function cover() { return $this->images()->findBy('name', 'cover') ?? $this->image(); } }
This works for model methods as well as for custom page methods, file methods or other methods defined in plugins.
Kirby::plugin('your-name/your-plugin', [ 'pageMethods' => [ /** * @kql-allowed */ 'cover' => function () { return $this->images()->findBy('name', 'cover') ?? $this->image(); } ] ]);
Blocking Methods
You can block individual class methods that would normally be accessible by listing them in your config:
return [ 'kql' => [ 'methods' => [ 'blocked' => [ 'Kirby\Cms\Page::url' ] ] ] ];
Blocking Classes
Sometimes you might want to reduce access to various parts of the system. This can be done by blocking individual methods (see above) or by blocking entire classes.
return [ 'kql' => [ 'classes' => [ 'blocked' => [ 'Kirby\Cms\User' ] ] ] ];
Now, access to any user is blocked.
Custom Classes and Interceptors
If you want to add support for a custom class or a class in Kirby's source that is not supported yet, you can list your own interceptors in your config
return [ 'kql' => [ 'interceptors' => [ 'Kirby\Cms\System' => 'SystemInterceptor' ] ] ];
You can put the class for such a custom interceptor in a plugin for example.
class SystemInterceptor extends Kirby\Kql\Interceptors\Interceptor { public const CLASS_ALIAS = 'system'; protected $toArray = [ 'isInstallable', ]; public function allowedMethods(): array { return [ 'isInstallable', ]; } }
Interceptor classes are pretty straight forward. With the CLASS_ALIAS you can give objects with that class a short name for KQL queries. The $toArray property lists all methods that should be rendered if you don't run a subquery. I.e. in this case kirby.system would render an array with the isInstallable value.
The allowedMethods method must return an array of all methods that can be access for this object. In addition to that you can also create your own custom methods in an interceptor that will then become available in KQL.
class SystemInterceptor extends Kirby\Kql\Interceptors\Interceptor { ... public function isReady() { return 'yes it is!'; } }
This custom method can now be used with kirby.system.isReady in KQL and will return yes it is!
Unintercepted Classes
If you want to fully allow access to an entire class without putting an interceptor in between, you can add the class to the allow list in your config:
return [ 'kql' => [ 'classes' => [ 'allowed' => [ 'Kirby\Cms\System' ] ] ] ];
This will introduce full access to all public class methods. This can be very risky though and you should avoid this if possible.
No Mutations
KQL only offers access to data in your site. It does not support any mutations. All destructive methods are blocked and cannot be accessed in queries.
Plugins
What's Kirby?
- getkirby.com – Get to know the CMS.
- Try it – Take a test ride with our online demo. Or download one of our kits to get started.
- Documentation – Read the official guide, reference and cookbook recipes.
- Issues – Report bugs and other problems.
- Feedback – You have an idea for Kirby? Share it.
- Forum – Whenever you get stuck, don't hesitate to reach out for questions and support.
- Discord – Hang out and meet the community.
- Mastodon – Spread the word.
- Bluesky – Spread the word.
License
MIT License © 2020-2023 Bastian Allgeier
getkirby/kql 适用场景与选型建议
getkirby/kql 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 26.99k 次下载、GitHub Stars 达 150, 最近一次更新时间为 2020 年 07 月 21 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「json」 「api」 「cms」 「query」 「headless」 「kirby」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 getkirby/kql 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 getkirby/kql 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 getkirby/kql 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
Kinikit - PHP Application development framework MVC component
GraphQL authentication for your headless Craft CMS applications.
Set Links with a specific language parameter
Supercharged text field validation.
ext-json wrapper with sane defaults
A package to cast json fields, each sub-keys is castable
统计信息
- 总下载量: 26.99k
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 150
- 点击次数: 11
- 依赖项目数: 0
- 推荐数: 2
其他信息
- 授权协议: MIT
- 更新时间: 2020-07-21