PHP code example of maiscraft / graphql

1. Go to this page and download the library: Download maiscraft/graphql library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

maiscraft / graphql example snippets


namespace App\Domain\Entity;

use Maiscraft\GraphQL\Entity\EntityObject;

class Article extends EntityObject
{
    public function __construct(array $data = [])
    {
        parent::__construct($data);
    }

    // id: int → 自动推断为 ID 类型,无需 #[Field(type: 'ID')]
    public function getId(): ?int
    {
        return $this->getData('id') ?: null;
    }

    public function getTitle(): string
    {
        return $this->getData('title') ?? '';
    }

    // is* 方法 → is_published 布尔字段
    public function isPublished(): bool
    {
        return $this->getData('status') === 'published';
    }
}

namespace App\Domain\Entity;

use Maiscraft\GraphQL\Annotation\Type;

#[Type(description: '文章')]
class Article
{
    public function __construct(
        private readonly ?int $id = null,
        private readonly string $title = '',
    ) {}

    public function getId(): ?int { return $this->id; }
    public function getTitle(): string { return $this->title; }
}

namespace App\Domain\ValueObject;

use Maiscraft\GraphQL\Annotation\Type;

#[Type(description: '发布状态')]
enum PublishStatus
{
    case DRAFT;
    case PUBLISHED;
    case ARCHIVED;
}

namespace App\DTO;

class CreateArticleDTO
{
    public function __construct(
        public readonly string $title,
        public readonly string $slug,
        public readonly ?string $description = null,
    ) {}
}

namespace App\GraphQL;

use App\Domain\Entity\Article;
use App\DTO\CreateArticleDTO;
use Maiscraft\GraphQL\Annotation\Query;
use Maiscraft\GraphQL\Annotation\Mutation;
use Maiscraft\GraphQL\Annotation\Arg;
use Maiscraft\GraphQL\Contract\ResolverInterface;

class ArticleResolver implements ResolverInterface
{
    public function __construct(
        private ArticleRepositoryInterface $repository
    ) {}

    // id: int → 自动推断为 ID! 类型,无需 #[Arg(type: 'ID!')]
    #[Query(description: 'Get an article by ID')]
    public function article(int $id): ?Article
    {
        return $this->repository->findById($id);
    }

    // 返回类型以 Pagination 结尾 → 自动追加分页参数 page/perPage
    #[Query(description: 'Get paginated articles')]
    public function articles(): ArticlePagination
    {
        return $this->repository->paginate();
    }

    #[Mutation(description: 'Create a new article')]
    public function createArticle(CreateArticleDTO $input): Article
    {
        return $this->repository->save(new Article(
            title: $input->title,
            slug: $input->slug,
            description: $input->description,
        ));
    }

    // id: int → 自动推断为 ID! 类型
    #[Mutation(description: 'Delete an article')]
    public function deleteArticle(int $id): bool
    {
        return $this->repository->delete($id);
    }
}

// productId 不是 "id",不会自动推断,需显式声明
#[Query(description: 'Get product variants')]
public function productVariants(#[Arg(type: 'ID!')] int $productId): array
{
    return $this->repository->getVariants($productId);
}

// config/autoload/graphql.php
return [
    'debug' => true,

    // 目录路径扫描(从文件内容解析 namespace 获取类名)
    'scan_paths' => [
        BASE_PATH . '/app',
    ],

    'security' => [
        'max_query_depth' => 15,
        'max_query_complexity' => 1000,
    ],
];

use Maiscraft\GraphQL\Default\SimpleContainer;
use Maiscraft\GraphQL\Driver\Driver;

$container = new SimpleContainer();
$driver = new Driver($container);

$schema = $driver->buildSchema(
    resolvers: [ArticleResolver::class],
    types: [Article::class, PublishStatus::class],
);

$result = \GraphQL\GraphQL::executeQuery(
    schema: $schema,
    source: '{ articles { items { id title } total } }',
);

use Maiscraft\GraphQL\Entity\EntityObject;
use Maiscraft\GraphQL\Annotation\Field;
use Maiscraft\GraphQL\Annotation\Input;

class Product extends EntityObject
{
    public ProductType $type = ProductType::STANDARD;
    public string $title = '';
    public ?float $price = null;

    // 只读字段,不出现在 Input 类型中
    #[Input(exclude: true)]
    public array $available_locales = [];

    // 自定义 getter 覆盖 __call 兜底
    public function getPriceInfo(): ?PriceInfo
    {
        return $this->getData('price_info');
    }
}

// Hyperf ConfigProvider
use Maiscraft\GraphQL\Contract\IdCodecInterface;

'dependencies' => [
    IdCodecInterface::class => fn($container) => $container->get(MyIdCodecAdapter::class),
],

use Maiscraft\GraphQL\Contract\IdCodecInterface;

final class MyIdCodec implements IdCodecInterface
{
    public function encode(int $id): string
    {
        return base_convert($id ^ 0x5DE3CE7, 10, 36);
    }

    public function decode(string $encoded): int
    {
        return base_convert($encoded, 36, 10) ^ 0x5DE3CE7;
    }

    public function decodeNullable(?string $encoded): ?int
    {
        if ($encoded === null || $encoded === '') {
            return null;
        }
        return $this->decode($encoded);
    }
}

$scalarOverrides = [];
$idType = $typeRegistry->get('ID');
if ($idType instanceof CustomScalarType) {
    $scalarOverrides['ID'] = $idType;
}

return new Schema([
    // ...
    'scalarOverrides' => $scalarOverrides,
]);

// 自动推断(无需注解)
#[Query]
public function article(int $id): ?Article { ... }  // → ID!

public function getId(): ?int { ... }  // → ID

// 需显式注解(非 id 名称)
#[Query]
public function productVariants(#[Arg(type: 'ID!')] int $productId): array { ... }

#[Field(type: 'ID')]
public function getCategoryId(): ?int { ... }

// 完整偏移分页 — 自动追加 page/perPage 参数
#[Query(type: 'PagePagination', description: '页面列表')]
public function pages(): \Hyperf\Contract\LengthAwarePaginatorInterface
{
    return $this->service->paginate();
}

// 带过滤参数 + 分页(分页参数自动追加)
#[Query(type: 'ProductPagination', description: '商品列表')]
public function products(
    #[Arg(name: 'filters', type: '[FilterInput!]')] array $filters = [],
): \Hyperf\Contract\LengthAwarePaginatorInterface
{
    return $this->service->paginate($filters);
}

// 游标分页 — 自动追加 first/after 参数
#[Query(type: 'CursorPagination<Product>', description: '商品游标分页')]
public function productsCursor(): array
{
    return PaginationHelper::cursorPaginate($query, $first, $after);
}

use Maiscraft\GraphQL\Pagination\PaginationHelper;

// 完整偏移分页(触发 COUNT(*))
$result = PaginationHelper::paginate($query, page: 2, perPage: 15);

// 简单分页(避免 COUNT(*),多取 1 条判断 hasMore)
$result = PaginationHelper::simplePaginate($query, page: 2, perPage: 15);

// Relay 游标分页
$result = PaginationHelper::cursorPaginate($query, first: 15, after: $cursor);

// 从 Hyperf Paginator 构建
$result = PaginationHelper::fromPaginator($paginator);

// 游标编解码
$cursor = PaginationHelper::encodeCursor(30);      // base64("cursor:30")
$offset = PaginationHelper::decodeCursor($cursor);  // 30

class Category extends EntityObject
{
    // 分页字段 — 只读,不出现在 Input 中
    #[Field(type: 'ProductPagination', description: '分类下商品(分页)')]
    #[Input(exclude: true)]
    public array $products = [];
}

#[Type(name: 'CustomName', description: '描述', hidden: false)]
class Article { ... }

#[Field(type: 'ProductPagination', description: '商品列表', hidden: false)]
public array $products = [];

#[Field(readonly: true, view: ['detail'])]
public ?string $description = null;

#[Field(writeonly: true, type: '[ID!]')]
public array $category_ids = [];

#[Type(name: 'Product')]
class Product extends EntityObject
{
    #[Field]
    public string $name;

    #[Field(readonly: true)]
    public string $slug;

    #[Field(readonly: true, view: ['detail'])]
    public ?string $description;

    #[Field(readonly: true, view: ['detail'])]
    public array $variants = [];

    #[Field(readonly: true, view: ['list'])]
    public ?float $min_price;

    #[Field(writeonly: true, type: '[ID!]')]
    public array $category_ids = [];
}

#[Query(name: 'articles', type: 'PagePagination', description: '文章列表')]
public function articles(): mixed { ... }

#[Mutation(name: 'createArticle', description: '创建文章')]
public function createArticle(CreateArticleDTO $input): Article { ... }

#[Arg(name: 'filters', type: '[FilterInput!]', description: '过滤条件')]
array $filters = []

// 从 Input 类型中排除(只读/计算字段)
#[Input(exclude: true)]
public array $available_locales = [];

// 标记为可选(即使 PHP 类型非空)
#[Input(

class CreateProductDTO
{
    #[Rules(['(['�式简写
    #[Rules('

// 类级别 — 该 Resolver 所有字段的默认权限
#[Permission(['content.view'])]
class PageAdminResolver implements AuthResolverInterface { ... }

// 方法级别 — 与类级权限合并
#[Permission(['content.create'])]
public function createPage(CreatePageDTO $input): Page { ... }

// 使用自定义策略类
#[Permission(policy: 'App\Policies\ProductPolicy')]
public function deleteProduct(int $id): bool { ... }

// 闭包判断 — 拒绝时返回 null(不抛错)
#[Privacy(fn($root, $args, $ctx) => $ctx?->is_admin)]
public string $hiddenField;

// 引用 Privacy 子类
#[Privacy(AdminPrivacy::class)]
public function getSecretData(): string { ... }

// 公开数据,所有用户共享同一缓存
#[Cache(ttl: 300)]
public function products(): array { ... }

// 按登录状态区分
#[Cache(ttl: 600, guard: true)]
public function recommendations(): array { ... }

// 按用户 ID 区分
#[Cache(ttl: 300, userId: true)]
public function myOrders(): array { ... }

// 按权限区分
#[Cache(ttl: 600, abilities: ['commerce:view', 'commerce:manage'])]
public function dashboardStats(): array { ... }

#[QueryMiddleware(priority: 50)]
class BodyHashMiddleware implements MiddlewareInterface { ... }

#[FieldMiddleware(priority: 50)]
class MyFieldMiddleware implements FieldMiddlewareInterface { ... }

#[GraphqlInterface(name: 'Node', description: '节点接口')]
interface NodeInterface { ... }

#[Union(name: 'SearchResult', description: '搜索结果联合类型')]
class SearchResultUnion
{
    public function types(): array { return [Product::class, Page::class]; }
}

// ❌ 废弃,改用 #[Permission]
#[Guard('admin')]
class AdminResolver { ... }

// ✅ 替代方案
#[Permission(['admin.access'])]
class AdminResolver { ... }