genko.meDocs

導入

PHPでgenko.meの配信APIにcurl_*関数で接続し、記事一覧と記事詳細を取得してHTMLに表示するまでの手順。

このページでは、PHPからgenko.meのblogAPIに接続し、記事一覧と記事詳細を取得します。使うのは標準のcURL拡張とjson拡張だけです。

用意するもの

  • genko.meのアカウントとワークスペース。APIの作成とAPIキーの発行にはオーナー権限が必要です。
  • PHP 8.1以降。以下のコードはコンストラクタプロモーションとreadonlyプロパティを使います。cURL拡張(ext-curl)とjson拡張(ext-json)が有効であること。php -m | grep -E 'curl|json'で確認できます。
  • リスト形式のAPIが1つと、公開済みの記事が1件。まだない場合はクイックスタートの手順2・3で作れます。

以降のyour-workspaceは自分のワークスペースIDに、blogは自分のエンドポイントに置き換えます。

インストール

ありません。 Composerで追加するパッケージはなく、以下のコードをプロジェクトに置くだけです。

APIキーとワークスペースIDを設定する

管理画面のURLがhttps://genko.me/w/your-workspaceなら、ワークスペースIDはyour-workspaceです。

APIキーは「設定・プラン」の「APIキー」タブ(/w/your-workspace/settings/api-keys)で作成します。「選択したAPIのみ」でblogだけを許可しておくと、キーが漏れたときの範囲を限定できます。作成直後に表示される値は後から再表示できません。

キーは環境変数から読みます。ApacheならSetEnv、php-fpmならenv[GENKO_API_KEY]、DockerやPaaSならその環境変数の仕組みを使ってください。

php
$apiKey = getenv('GENKO_API_KEY');

キーをPHPファイルに直接書かないでください。 どうしても設定ファイルに置く場合は、ドキュメントルートの外に置き、Gitの追跡対象から外します。.envをドキュメントルート直下に置くと、設定によっては素のテキストとして配信されます。

クライアントを書く

配信APIへのGETを1か所にまとめます。

src/Genko.php
<?php
declare(strict_types=1);
const GENKO_BASE_URL = 'https://api.genko.me';const GENKO_WORKSPACE = 'your-workspace';
final class GenkoApiError extends RuntimeException{}
/** * 配信APIをGETして、デコード済みの連想配列を返す。 * * @param array<string, string|int> $queries * @return array<string, mixed> */function genko_get(string $path, array $queries = []): array{    $url = GENKO_BASE_URL . '/v1/' . GENKO_WORKSPACE . $path;    if ($queries !== []) {        $url .= '?' . http_build_query($queries);    }
    $curl = curl_init($url);    curl_setopt_array($curl, [        CURLOPT_RETURNTRANSFER => true,        CURLOPT_TIMEOUT => 10,        CURLOPT_HTTPHEADER => ['X-API-KEY: ' . getenv('GENKO_API_KEY')],    ]);
    $body = curl_exec($curl);    if ($body === false) {        throw new GenkoApiError('配信APIへ接続できませんでした: ' . curl_error($curl));    }
    $status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);    $decoded = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
    if ($status >= 400) {        throw new GenkoApiError($decoded['message'] ?? '原因不明のエラーです。', $status);    }
    return $decoded;}

http_build_query()を使うと、filtersの角括弧や日本語の値がURLエンコードされます。クエリ文字列を自分で連結しないでください。

curl_close()はPHP 8.0以降で意味がなくなり、8.5では非推奨になったため呼んでいません。$curlはスコープを抜けると解放されます。

記事一覧を出す

public/blog.php
<?php
require __DIR__ . '/../src/Genko.php';
$list = genko_get('/blog', [    'limit' => 10,    'orders' => '-publishedAt',    'fields' => 'title,publishedAt',]);
?><h1>ブログ(全<?= (int) $list['totalCount'] ?>件)</h1><ul><?php foreach ($list['contents'] as $post): ?>    <li>        <a href="/blog.php?id=<?= rawurlencode($post['id']) ?>">            <?= htmlspecialchars($post['title'], ENT_QUOTES, 'UTF-8') ?>        </a>    </li><?php endforeach; ?></ul>

レスポンスはcontents・totalCount・offset・limitを持つ連想配列です。該当する記事がない場合もエラーにはならず、contentsが空配列になります。

テキストフィールドは必ずhtmlspecialchars()を通してください。 配信APIはテキストをエスケープせずに返します。

型を付けて受け取る

連想配列のまま扱うとフィールドIDの打ち間違いに気づけません。スキーマに合わせたクラスを1つ書いておくと、IDEの補完も効きます。

src/Post.php
<?php
declare(strict_types=1);
final class Post{    public function __construct(        public readonly string $id,        public readonly string $title,        public readonly string $body,        public readonly ?string $category,        /** @var list<string> */        public readonly array $tags,        public readonly ?DateTimeImmutable $publishedAt,    ) {    }
    /** @param array<string, mixed> $entry */    public static function fromEntry(array $entry): self    {        return new self(            id: $entry['id'],            title: $entry['title'] ?? '',            body: $entry['body'] ?? '',            // 任意項目や、あとから追加したフィールドは古い記事に無いことがある。            category: $entry['category'] ?? null,            tags: $entry['tags'] ?? [],            publishedAt: isset($entry['publishedAt'])                ? new DateTimeImmutable($entry['publishedAt'])                : null,        );    }}

fieldsで項目を絞ると、指定しなかったフィールドはレスポンスに含まれません。??での既定値は省略しないでください。

php
$posts = array_map(Post::fromEntry(...), $list['contents']);

記事詳細を出す

一覧で得たidをパスの末尾に付けます。

public/post.php
<?php
require __DIR__ . '/../src/Genko.php';require __DIR__ . '/../src/Post.php';
$id = $_GET['id'] ?? '';
try {    $post = Post::fromEntry(genko_get('/blog/' . rawurlencode($id)));} catch (GenkoApiError $error) {    if ($error->getCode() === 404) {        http_response_code(404);        exit('記事が見つかりません。');    }    throw $error;}
?><article>    <h1><?= htmlspecialchars($post->title, ENT_QUOTES, 'UTF-8') ?></h1>    <?php if ($post->publishedAt !== null): ?>        <time datetime="<?= $post->publishedAt->format(DateTimeInterface::ATOM) ?>">            <?= $post->publishedAt->setTimezone(new DateTimeZone('Asia/Tokyo'))->format('Y年n月j日') ?>        </time>    <?php endif; ?>    <?= $post->body ?></article>

一覧と違い、詳細のレスポンスはcontentsで包まれず、コンテンツの連想配列がそのまま返ります。非公開・削除済み・存在しないIDはどれも404です。

日時はISO 8601のUTC文字列で届きます。表示前にAsia/Tokyoへ変換してください。

リッチテキスト(body)はHTML文字列なので、htmlspecialchars()を通さずに出力します。管理画面で入力された内容がそのまま出るため、信頼できる書き手だけに編集権限を渡してください。 外部から投稿を受け付ける構成では、HTML Purifierなどでサニタイズしてから出力します。

絞り込む

filtersはフィールドIDと演算子を組み合わせた文字列です。

php
$tech = genko_get('/blog', [    'filters' => 'category[equals]tech',    'orders' => '-publishedAt',    'limit' => 10,]);
$recent = genko_get('/blog', [    'filters' => 'publishedAt[greater_than]' . (new DateTimeImmutable('-30 days'))->format('Y-m-d\TH:i:s.v\Z'),]);

演算子の一覧と、[and]/[or]での連結はクエリ仕様を参照してください。

エラーの扱い

ステータス主な原因
400クエリの形式・値・演算子が不正
401APIキーがない、無効、期限切れ
403キーとワークスペースが一致しない
404ワークスペース・API・コンテンツがない、またはキーの対象外
429同じAPIキーで1分300リクエストを超えた

上のクライアントはGenkoApiErrorのコードにHTTPステータスを入れています。$error->getCode()で分岐してください。詳しくはエラーと制限にまとめています。

次に読む

ページ送り、保存済み下書きのプレビュー、Webhookの署名検証は実践に続きます。

最終更新: 2026年9月17日

目次