aponahmed/laravel-miniql 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

aponahmed/laravel-miniql

Composer 安装命令:

composer require aponahmed/laravel-miniql

包简介

A Laravel GraphQL-lite internal query engine — single endpoint, schema-driven, N+1-safe, mutation-ready.

README 文档

README

PHP Laravel License

A production-grade, single-endpoint internal query engine for Laravel.
Think GraphQL — without the schema language, without the AST parser, with full Laravel idioms.

Table of Contents

  1. What is MiniQL?
  2. Installation
  3. Quick Start
  4. Query Syntax Reference
  5. Mutation Syntax Reference
  6. Schema Config Reference
  7. Resolvers
  8. Mutation Handlers
  9. Hooks
  10. Fluent PHP API
  11. Caching
  12. Security
  13. Introspection
  14. Artisan Commands
  15. Architecture Overview
  16. Upgrading / Roadmap

What is MiniQL?

MiniQL lets your frontend (or internal services) send expressive queries to a single POST endpoint instead of building dozens of REST routes.

POST /api/miniql

A request looks like this:

{
  "query": {
    "users": {
      "where":    { "active": true },
      "fields":   ["id", "name", "email"],
      "relations": {
        "posts": {
          "fields":  ["id", "title"],
          "orderBy": { "column": "created_at", "direction": "desc" },
          "limit":   5
        }
      },
      "orderBy": { "column": "name", "direction": "asc" },
      "page":    1,
      "perPage": 20
    }
  }
}

Everything you send is validated against a whitelist schema. No raw SQL exposure. No N+1.

Installation

composer require aponahmed/laravel-miniql

Publish the config:

php artisan vendor:publish --tag=miniql-config

Quick Start

1. Register your models in config/miniql.php

'models' => [

    'users' => [
        'model'     => App\Models\User::class,
        'fields'    => ['id', 'name', 'email', 'created_at'],
        'relations' => ['posts'],
        'mutations' => [
            'createUser' => App\MiniQL\Mutations\CreateUserMutation::class,
            'updateUser' => App\MiniQL\Mutations\UpdateUserMutation::class,
        ],
    ],

    'posts' => [
        'model'     => App\Models\Post::class,
        'fields'    => ['id', 'title', 'body', 'user_id'],
        'relations' => ['user'],
        'mutations' => [
            'createPost' => App\MiniQL\Mutations\CreatePostMutation::class,
        ],
    ],

],

2. Send a query

curl -X POST http://yourapp.test/api/miniql \
  -H "Content-Type: application/json" \
  -d '{
    "query": {
      "users": {
        "fields": ["id", "name"],
        "limit": 5
      }
    }
  }'

Response:

{
  "data": {
    "users": [
      { "id": 1, "name": "Alice" },
      { "id": 2, "name": "Bob" }
    ]
  },
  "errors": []
}

Query Syntax Reference

Every query node supports:

Key Type Description
fields string[] Fields to return (whitelist enforced)
where object Simple equality OR advanced {op, value} filter
whereIn object Column → array of values
whereNull string[] Columns that must be NULL
whereNotNull string[] Columns that must NOT be NULL
search object Full-text LIKE search {term, fields[]}
orderBy string|object String column, or {column, direction}
limit int Max rows (hard-capped by security.max_results)
page int Enable pagination (returns {data, meta})
perPage int Rows per page (default from config)
relations object Eager-load nested relations (recursively supported)

Advanced where example

{
  "query": {
    "users": {
      "where": {
        "age":    { "op": ">=", "value": 18 },
        "status": "active"
      },
      "whereIn":     { "role": ["admin", "editor"] },
      "whereNotNull": ["email_verified_at"],
      "search": { "term": "alice", "fields": ["name", "email"] }
    }
  }
}

Pagination example

{
  "query": {
    "posts": {
      "fields":  ["id", "title", "created_at"],
      "orderBy": { "column": "created_at", "direction": "desc" },
      "page":    2,
      "perPage": 10
    }
  }
}

Response shape with pagination:

{
  "data": {
    "posts": {
      "data": [ ... ],
      "meta": {
        "current_page": 2,
        "per_page": 10,
        "total": 87,
        "last_page": 9,
        "from": 11,
        "to": 20
      }
    }
  },
  "errors": []
}

Multi-type query (batch)

{
  "query": {
    "users": { "fields": ["id", "name"], "limit": 3 },
    "posts": { "fields": ["id", "title"], "limit": 3 }
  }
}

Both are resolved in a single request. No N+1. Each type runs its own eager-load strategy.

Mutation Syntax Reference

{
  "mutation": {
    "createUser": {
      "data": {
        "name":     "Apon",
        "email":    "apon@example.com",
        "password": "secret123"
      }
    }
  }
}
  • All mutations run inside a database transaction. If any mutation fails, all are rolled back.
  • Multiple mutations can be sent in one request.
{
  "mutation": {
    "createUser": { "data": { "name": "Bob", "email": "bob@x.com", "password": "pass1234" } },
    "createPost": { "data": { "title": "Hello World", "user_id": 1 } }
  }
}

Query + Mutation in one request

{
  "mutation": {
    "updateUser": { "id": 3, "data": { "name": "Updated" } }
  },
  "query": {
    "users": { "where": { "id": 3 }, "fields": ["id", "name"] }
  }
}

Mutations run first, then queries — so the query returns the updated state.

Schema Config Reference

Full annotated config (published to config/miniql.php):

'models' => [
    'users' => [

        // ✅ Required: Eloquent model class
        'model' => App\Models\User::class,

        // ✅ Required: whitelisted selectable fields
        'fields' => ['id', 'name', 'email', 'created_at'],

        // Whitelisted eager-loadable relation names
        'relations' => ['posts', 'profile'],

        // Custom resolver (overrides default query builder)
        'resolver' => App\MiniQL\Resolvers\UserResolver::class,

        // Mutation handlers, keyed by mutation name
        'mutations' => [
            'createUser' => App\MiniQL\Mutations\CreateUserMutation::class,
            'updateUser' => App\MiniQL\Mutations\UpdateUserMutation::class,
            'deleteUser' => App\MiniQL\Mutations\DeleteUserMutation::class,
        ],

        // Before/after hooks for query and mutation lifecycle
        'hooks' => [
            'before_query'    => App\MiniQL\Hooks\UserBeforeQueryHook::class,
            'after_query'     => App\MiniQL\Hooks\UserAfterQueryHook::class,
            'before_mutation' => null,
            'after_mutation'  => null,
        ],

        // Global Eloquent scopes applied to every query on this type
        // e.g. 'active' calls ->active() on the query builder
        'scopes' => ['active'],

        // Free-form metadata (documentation, versioning, etc.)
        'meta' => ['description' => 'Registered users'],
    ],
],

Resolvers

A Resolver replaces the default query builder for a specific type. Use it for:

  • Multi-tenant scoping
  • Auth-based filtering
  • Complex joins that can't be expressed in where

Generate a resolver

php artisan miniql:make-resolver UserResolver

Implement it

// app/MiniQL/Resolvers/UserResolver.php

namespace App\MiniQL\Resolvers;

use MiniQL\Resolvers\BaseResolver;
use Illuminate\Database\Eloquent\Builder;

class UserResolver extends BaseResolver
{
    protected function model(): string
    {
        return \App\Models\User::class;
    }

    public function query(array $node): Builder
    {
        $q = parent::query($node); // applies basic where filters

        // Scope to current tenant
        $q->where('tenant_id', auth()->user()->tenant_id);

        return $q;
    }
}

Register it

// config/miniql.php
'users' => [
    'resolver' => App\MiniQL\Resolvers\UserResolver::class,
    ...
],

Mutation Handlers

Generate a mutation

php artisan miniql:make-mutation CreateUser

Implement it

// app/MiniQL/Mutations/CreateUserMutation.php

namespace App\MiniQL\Mutations;

use MiniQL\Mutations\BaseMutation;
use App\Models\User;
use Illuminate\Support\Facades\Hash;

class CreateUserMutation extends BaseMutation
{
    protected function rules(): array
    {
        return [
            'data.name'     => 'required|string|max:255',
            'data.email'    => 'required|email|unique:users,email',
            'data.password' => 'required|string|min:8',
        ];
    }

    public function handle(array $node): mixed
    {
        $data = $this->validate($node); // throws ValidationException on failure

        return User::create([
            'name'     => $data['name'],
            'email'    => $data['email'],
            'password' => Hash::make($data['password']),
        ]);
    }
}

$this->validate($node) runs Laravel's Validator against rules(). On failure it throws a ValidationException that is caught by the controller and returned in the errors key.

Hooks

Hooks let you tap into the query/mutation lifecycle without modifying the engine.

Hook Receives Called
before_query &$node (array) Before the query executes
after_query &$result (mixed) After query result is built
before_mutation &$node (array) Before mutation handler runs
after_mutation &$result (mixed) After mutation handler returns
// app/MiniQL/Hooks/AuditAfterMutationHook.php

namespace App\MiniQL\Hooks;

use MiniQL\Contracts\HookInterface;

class AuditAfterMutationHook implements HookInterface
{
    public function handle(mixed &$context): void
    {
        logger()->info('[Audit] Mutation completed.', ['result' => $context]);
    }
}

Register in config:

'hooks' => [
    'after_mutation' => App\MiniQL\Hooks\AuditAfterMutationHook::class,
],

Fluent PHP API

Use MiniQL directly in PHP (controllers, jobs, services) without HTTP:

use MiniQL\Facades\MiniQL;

// Simple
$users = MiniQL::query('users')
    ->fields(['id', 'name', 'email'])
    ->where('active', true)
    ->whereIn('role', ['admin', 'editor'])
    ->orderBy('name', 'asc')
    ->limit(50)
    ->get();

// Paginated
$result = MiniQL::query('posts')
    ->fields(['id', 'title', 'created_at'])
    ->with('user', ['fields' => ['id', 'name']])
    ->orderBy('created_at', 'desc')
    ->paginate(15, 1)
    ->get();
// $result['data'], $result['meta']

// Full payload
$result = MiniQL::execute([
    'query' => [
        'users' => ['fields' => ['id', 'name'], 'limit' => 5],
    ],
    'mutation' => [
        'createUser' => ['data' => ['name' => 'Eve', 'email' => 'eve@x.com', 'password' => 'pass1234']],
    ],
]);

Caching

Enable Redis-backed query caching in .env:

MINIQL_CACHE=true
MINIQL_CACHE_TTL=120
MINIQL_CACHE_STORE=redis

Cache keys are derived from type + md5(node) — identical queries return cached results instantly.

Cache is automatically invalidated per-type when a mutation runs (if cache.auto_invalidate = true).

Security

All security options live in config/miniql.php under security:

Option Default Description
max_depth 5 Max relation nesting depth
max_query_nodes 10 Max top-level query types per request
max_results 1000 Hard cap on rows returned
require_auth false Set to true to require authentication globally
rate_limit 60 Requests per minute per IP (0 = disabled)

Field & relation whitelisting is always enforced — there is no way to query a field not listed in config('miniql.models.*.fields').

Protect the endpoint by adding auth:sanctum (or any Laravel middleware) to the route middleware:

// config/miniql.php
'route' => [
    'middleware' => ['api', 'auth:sanctum'],
],

Introspection

GET /api/miniql/schema

Returns the full registered schema as JSON (useful for frontend tooling):

{
  "schema": {
    "users": {
      "fields":    ["id", "name", "email", "created_at"],
      "relations": ["posts"],
      "mutations": ["createUser", "updateUser", "deleteUser"],
      "meta":      {}
    }
  },
  "version": "1.0"
}

Disable in production:

MINIQL_INTROSPECTION=false

Or protect with middleware:

'introspection' => [
    'enabled'    => true,
    'middleware' => ['auth:sanctum'],
],

Artisan Commands

Command Description
miniql:make-resolver Name Scaffold a resolver class
miniql:make-mutation Name Scaffold a mutation handler class
miniql:schema-dump Pretty-print the registered schema
miniql:schema-dump --json Output schema as raw JSON
miniql:schema-validate Verify all model/resolver/mutation classes exist on disk

Architecture Overview

POST /api/miniql
       │
       ▼
MiniQLController
       │
       ├─ SchemaValidator       ← whitelist check (fields, relations, depth)
       │
       ├─ QueryEngine
       │      ├─ ResolverInterface (custom) OR default Eloquent builder
       │      ├─ Eager loading  ← N+1 prevention via ->with()
       │      ├─ QueryCache     ← Redis-backed result cache
       │      └─ Hooks          ← before/after lifecycle
       │
       └─ MutationEngine
              ├─ DB::transaction ← atomic multi-mutation
              ├─ BaseMutation    ← Laravel validation built-in
              └─ Hooks           ← before/after lifecycle

Roadmap

Possible future upgrades:

  • Query complexity scoring (prevent expensive queries by weight)
  • String DSL parser — accept users { id name posts { title } } syntax
  • WebSocket subscriptions — real-time updates on mutations
  • Persisted queries — hash-indexed pre-registered queries
  • Automatic SQL optimization — detect and merge redundant joins
  • OpenAPI export — generate an OpenAPI 3 spec from the schema config

License

MIT © Muhiminul Haque

aponahmed/laravel-miniql 适用场景与选型建议

aponahmed/laravel-miniql 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 2 次下载、GitHub Stars 达 2, 最近一次更新时间为 2026 年 04 月 22 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「rest」 「api」 「laravel」 「graphql」 「query-engine」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 aponahmed/laravel-miniql 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 aponahmed/laravel-miniql 我们能提供哪些服务?
定制开发 / 二次开发

基于 aponahmed/laravel-miniql 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 2
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 2
  • 点击次数: 39
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 2
  • Watchers: 0
  • Forks: 1
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-04-22