装依赖跑起来:

  cd /mnt/d/graphql
  curl -sS https://getcomposer.org/installer | php8.4
  php8.4 composer.phar install --no-security-blocking
  php8.4 graphql.php

  装好后测试:

  # 查用户+订单
  curl -X POST http://localhost:9600/graphql \
    -H 'Content-Type: application/json' \
    -d '{"query":"{user(id:1){name email orders{product price}}}"}'

  # 查所有用户
  curl -X POST http://localhost:9600/graphql \
    -d '{"query":"{users{id name}}"}'  \
    -H 'Content-Type: application/json'

  ---
  大白话解释:

  GraphQL 解决什么问题

  REST的问题:
  - 拿用户信息 → /api/user/1
  - 拿他的订单 → /api/orders?user=1
  - 拿订单里的商品 → /api/product/101

  3个请求,3次网络往返,前端还要自己拼数据。

  GraphQL 一个请求描述你要什么:
  {
    user(id: 1) {
      name
      orders {
        product
        price
      }
    }
  }
  服务器帮你全拿,一次返回。

  resolve 是什么

  每个字段都有一个 resolve 函数,告诉 GraphQL 这个字段的数据从哪来。可以查数据库、调RPC、读缓存,随便。

  异步在哪

  Swoole 协程下,每个请求跑在独立协程里。resolve 里如果用协程客户端查数据库,等待时不阻塞其他请求,多个字段的查询可以用WaitGroup
  并发打出去,原理和BFF那个一样。
<?php
/**
 * GraphQL 异步解析服务
 *
 * 大白话:
 *   REST API 要拿用户+订单+商品需要发3个请求
 *   GraphQL 一个请求描述你要什么,服务器帮你全拿回来
 *   异步的意思:解析每个字段时,多个数据库/服务查询同时发出,不排队等
 *
 * 流程:
 *   POST /graphql → 解析query → 并发执行各字段resolver → 拼结果返回
 *
 * 依赖:
 *   composer install
 *
 * 运行:
 *   php8.4 graphql.php
 */

require __DIR__ . '/vendor/autoload.php';

use GraphQL\GraphQL;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use Swoole\Http\Server;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\Coroutine;
use Swoole\Coroutine\WaitGroup;

// ── 模拟数据(实际换成数据库/RPC查询)────────────────────────
$users = [
    1 => ['id' => 1, 'name' => '张三', 'email' => 'zhang@example.com'],
    2 => ['id' => 2, 'name' => '李四', 'email' => 'li@example.com'],
];
$orders = [
    1 => [['id' => 101, 'product' => '手机', 'price' => 5999]],
    2 => [['id' => 102, 'product' => '电脑', 'price' => 8999]],
];

// ── 定义 GraphQL 类型 ─────────────────────────────────────────
// 告诉 GraphQL 每个类型长什么样,每个字段怎么取数据

$orderType = new ObjectType([
    'name'   => 'Order',
    'fields' => [
        'id'      => Type::int(),
        'product' => Type::string(),
        'price'   => Type::float(),
    ],
]);

$userType = new ObjectType([
    'name'   => 'User',
    'fields' => fn() => [
        'id'    => Type::int(),
        'name'  => Type::string(),
        'email' => Type::string(),
        // orders 字段:查这个用户的订单
        // resolve 就是"这个字段的数据从哪来"
        'orders' => [
            'type'    => Type::listOf($orderType),
            'resolve' => function ($user) use ($orders) {
                // 实际这里可以是协程查数据库
                // usleep 模拟查询耗时
                usleep(50000); // 50ms
                return $orders[$user['id']] ?? [];
            },
        ],
    ],
]);

// ── Query 入口:客户端能查什么 ────────────────────────────────
$queryType = new ObjectType([
    'name'   => 'Query',
    'fields' => [
        // 查单个用户
        'user' => [
            'type' => $userType,
            'args' => ['id' => Type::nonNull(Type::int())],
            'resolve' => function ($root, $args) use ($users) {
                usleep(30000); // 模拟30ms查询
                return $users[$args['id']] ?? null;
            },
        ],
        // 同时查多个用户(并发解析)
        'users' => [
            'type'    => Type::listOf($userType),
            'resolve' => function () use ($users) {
                return array_values($users);
            },
        ],
    ],
]);

$schema = new Schema(['query' => $queryType]);

// ── Swoole HTTP 服务 ──────────────────────────────────────────
$server = new Server('0.0.0.0', 9600);
$server->set([
    'worker_num'       => swoole_cpu_num(),
    'enable_coroutine' => true,
]);

$server->on('request', function (Request $req, Response $res) use ($schema) {

    $res->header('Content-Type', 'application/json');
    $res->header('Access-Control-Allow-Origin', '*');  // 允许浏览器直接调

    // GraphQL Playground 的 OPTIONS 预检请求
    if ($req->server['request_method'] === 'OPTIONS') {
        $res->header('Access-Control-Allow-Headers', 'Content-Type');
        $res->end();
        return;
    }

    if ($req->server['request_uri'] !== '/graphql') {
        $res->status(404);
        $res->end(json_encode(['error' => 'use POST /graphql']));
        return;
    }

    // 解析请求体
    $body = json_decode($req->rawContent(), true);
    if (!isset($body['query'])) {
        $res->status(400);
        $res->end(json_encode(['error' => 'missing query']));
        return;
    }

    // ── 执行 GraphQL 查询 ─────────────────────────────────────
    // webonyx/graphql-php 内部会按字段依次调用 resolve
    // 配合 Swoole 协程,每个 resolve 里的 IO 操作都可以异步
    try {
        $result = GraphQL::executeQuery(
            $schema,
            $body['query'],
            null,
            null,
            $body['variables'] ?? null
        );
        $res->end(json_encode($result->toArray()));
    } catch (\Throwable $e) {
        $res->status(500);
        $res->end(json_encode(['error' => $e->getMessage()]));
    }
});

$server->on('workerStart', function ($s, $wid) {
    if ($wid === 0) {
        echo "GraphQL → http://0.0.0.0:9600/graphql\n";
        echo "测试: curl -X POST http://localhost:9600/graphql \\\n";
        echo "  -H 'Content-Type: application/json' \\\n";
        echo "  -d '{\"query\":\"{user(id:1){name email orders{product price}}}\"}'  \n";
    }
});

$server->start();

composer.json

{
    "require": {
        "webonyx/graphql-php": "^15.0"
    },
    "config": {
        "audit": {
            "block-insecure": false
        }
    }
}
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐