PHPでMicroCMSブログを作ってみる!
MicroCMSとは日本製のヘッドレスCMSになります。
PHPのCMSと言えば、みなさんご存知WordPressですが、昨今ではApiベースのヘッドレスCMSというものが選択肢のひとつになってきました。
ただ、Zennの記事で「PHPでMicroCMSを使ってみた」みたいな記事が少なく、ほとんどの記事が「Jamstack」や「NextJS」などの、JS/TSでのMicroCMSの記事ばかりです・・・
どうしても「Webフロント=JS/TSフレームワーク」みたいなイメージがあるのですが、PHPerの自分としては、前から「PHPでも全然MicroCMSという選択肢ありだぜ!」という考えがあったので、今回試しにMicroCMS+PHPで簡単なブログアプリのお試し実装をしました。
注釈! HTMLコーディングがクソなのは目をつぶってください・・・
利用パッケージ
今回は、普通にLaravelなどのメジャーなフレームワークではなく、今まで使った事ないパッケージも使ってみたかったので「自作フレームワーク+使った事ないパッケージ」という構成で開発してみました。
ということで、主に以下のPHPパッケージを利用しました。
パッケージ | 説明 |
---|---|
Egg | 自作のオレオレWebフレームワーク |
Latte | テンプレートエンジン |
CycleORM | ORM(データベース操作) |
MicroCMS PHP SDK | MicroCMSのApiアクセス |
Latte
Latteは安全性が高く、直感的に扱える構文が用意されていて、非常にテンプレートの作成が捗るエンジンになります。
以下のように、とてもシンプルなテンプレートを定義できます。
{layout '@layout.layout'}
{block content}
<h1 n:block="title">Home</h1>
<ul class="mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-6 text-sm sm:mt-20 sm:grid-cols-2 md:gap-y-10 lg:max-w-none lg:grid-cols-2">
<li n:foreach="$blogs as $blog" class="rounded-2xl border border-gray-200 p-8 bg-white">
<a href="{route('blog.show', ['id' => $blog->id])}">
{if $blog->eyecatch}
<img src="{$blog->eyecatch}" class="w-64 rounded mb-6">
{/if}
<h3 class="font-semibold text-gray-900">
{$blog->title}
</h3>
</a>
</li>
</ul>
{/block}
{layout '@layout.layout'}
{block title}{$blog->title}{/block}
{block content}
<div class="flex items-center md:justify-start mb-3 lg:mb-5 text-xs md:text-sm leading-6 -tracking-[0.3px] lg:tracking-normal">
<a href="{route('home')}" class="text-gray-750">Home</a>
{if $blog->category}
<i class="fas fa-chevron-right text-gray-750 mx-[7px]" aria-hidden="true"></i>
<a href="{route('category.show', ['id' => $blog->category->id])}" class="text-gray-750">{$blog->category->name}</a>
{/if}
<i class="fas fa-chevron-right text-gray-750 mx-[7px]" aria-hidden="true"></i>
<a href="javascript:void(0)" class="text-black">{$blog->title}</a>
</div>
<div class="mx-auto max-w-2xl text-center">
<h1 class="text-3xl font-medium tracking-tight text-gray-900">
{$blog->title}
</h1>
<div class="m-3">
<span class="blog-date">{$blog->publishedAt|date:'Y.m.d'}</span>
</div>
{if $blog->eyecatch}
<img src="{$blog->eyecatch}" class="rounded">
{/if}
</div>
<div class="mx-auto mt-16 w-full">
<section class="overflow-hidden rounded-3xl md:p-12 p-6 shadow-lg shadow-gray-900/5 bg-white">
<article class="prose">
{$blog->content|noescape}
</article>
</section>
</div>
{/block}
CycleORM
CycleORMは、Spiral Frameworkで採用されているORMになります。
普段業務でPHPを使うときは、よくLaravelを使って開発をするので、Eloquentを多用しています。
ただ、Eloquentモデルは、ひとつのクラスでの責務が多すぎるし(データクラスやデータベース操作など責務が色々・・・)タイプヒントが中々出せなかったり、個人的に不満がありました。
CycleORMを利用すると、データベース操作・データクラスなどの責務がある程度分離できて、テストがしやすい印象があったので、今回使ってみました。
以下のようなEntityクラスを定義しました。
<?php
namespace App\Entity;
use App\Repository\BlogRepository;
use Cycle\Annotated\Annotation\Entity;
use Cycle\Annotated\Annotation\Column;
use Cycle\Annotated\Annotation\Relation\BelongsTo;
use DateTimeInterface;
#[Entity(
role: 'blog',
repository: BlogRepository::class,
table: 'blogs',
)]
class Blog
{
public function __construct(
#[Column(type: 'string', primary: true)]
public string $id,
#[Column(type: 'text', nullable: true)]
public ?string $eyecatch,
#[Column(type: 'string')]
public string $title,
#[Column(type: 'longText')]
public string $content,
#[Column(type: 'timestamp', name: 'created_at')]
public DateTimeInterface $createdAt,
#[Column(type: 'timestamp', name: 'updated_at')]
public DateTimeInterface $updatedAt,
#[Column(type: 'timestamp', name: 'published_at')]
public DateTimeInterface $publishedAt,
#[BelongsTo(target: Category::class, nullable: true)]
public ?Category $category = null,
) {
//
}
}
<?php
namespace App\Entity;
use App\Repository\CategoryRepository;
use Cycle\Annotated\Annotation\Entity;
use Cycle\Annotated\Annotation\Column;
use DateTimeInterface;
#[Entity(
role: 'category',
repository: CategoryRepository::class,
table: 'categories',
)]
class Category
{
public function __construct(
#[Column(type: 'string', primary: true)]
public string $id,
#[Column(type: 'string')]
public string $name,
#[Column(type: 'timestamp', name: 'created_at')]
public DateTimeInterface $createdAt,
#[Column(type: 'timestamp', name: 'updated_at')]
public DateTimeInterface $updatedAt,
) {
//
}
}
CycleORMだと、アノテーションを使えたりするので、非常にシンプルにデータクラスを実装できます!
注釈! 今回テスト実装なのでプロパティをpublicしているのですが、本来はprivateにした方が良いので注意してください
MicroCMS PHP SDK
MicroCMSへのApiアクセスを簡単にしてくれるパッケージです。
以下のような設定をすれば、すぐ利用できます。
<?php
use Microcms\Client;
// クライアントのApi設定をする
$client = new Client(
config('setting.microcms.domain'), // MicroCMS Api サブドメイン
config('setting.microcms.api-key'), // MicroCMS Api キー
),
// 記事のリストを取得する
$content = $client->list('blogs');
SSG or SSR
記事内容の出力については、SSG(静的サイトジェネレート)にするかSSR(サーバーサイドレンダリング)にするか悩みました。
今回、更新頻度が少ないブログサイトを想定しているので、テンプレートレンダリングでのSSGにしても良かったのですが、PHPをフルに利用できるようなサイトにしたかったので、SSRにしました。
ただ、ページにアクセスするたびにMicroCMSのApiにアクセスして、そのデータを直接テンプレートで表示するようなサイトも嫌だなーと思っていたので、以下のような設計を考えました。
- MicroCMSの公開記事データを、SQLiteのデータベースにインポートする。
- 記事を表示する時に、事前にSQLiteにインポートしたデータを参照して、テンプレートで表示する。
- 記事データが公開された場合は、再度SQLiteのデータベースにインポートする。
つまり、SSGのように記事が更新されたら静的HTMLを全て出力するように、記事が更新されたらデータベースにデータをインポートして、SSRでデータベースのデータを表示するシステムにしました。
更新が多いサイトだと、この方法では厳しいと思います(笑)
設置サーバー
システムの設置先サーバーをどうするかについてですが、格安のLolipopやXserverでも良かったのですが、テスト実装だし、あまりお金をかけたくなかったので、GoogleCloudRunでシステムを設置することにしました。
CloudRunへのデプロイについては、Github Actionsのワークフローで行なっています。
以下は、ワークフローの定義となります。
name: Cloud Run Deploy
on:
repository_dispatch:
types: [update_post]
env:
GCP_PROJECT: ${{ secrets.GCP_PROJECT }}
GCP_SERVICE: ${{ secrets.GCP_SERVICE }}
GCP_REGION: us-central1
MICROCMS_DOMAIN: ${{ secrets.MICROCMS_DOMAIN }}
MICROCMS_API_KEY: ${{ secrets.MICROCMS_API_KEY }}
DOTENV_PATH: ./.gcr/.deploy.env
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- run: git checkout main
## ソースからのデプロイになり.コンテナの細かい設定はできないので
## .envファイルを書き換えてコンテナをビルドするようにした
- name: Copy Environment
run: |
cp ./.gcr/.example.env $DOTENV_PATH
sed -i -e "s/=\${MICROCMS_DOMAIN}/=${MICROCMS_DOMAIN}/g" ${DOTENV_PATH}
sed -i -e "s/=\${MICROCMS_API_KEY}/=${MICROCMS_API_KEY}/g" ${DOTENV_PATH}
- name: GCloud Auth
uses: 'google-github-actions/auth@v1'
with:
credentials_json: '${{ secrets.GCP_SA_KEY }}'
- name: Deploy to Cloud Run
id: deploy
uses: google-github-actions/deploy-cloudrun@v1
with:
service: ${{ env.GCP_SERVICE }}
project_id: ${{ env.GCP_PROJECT }}
region: ${{ env.GCP_REGION }}
source: ./
- name: Show Output
run: echo ${{ steps.deploy.outputs.url }}
記事公開タイミングでのデータ更新について
記事が公開されたタイミングでのデータ更新をどうするか?についてですが、MicroCMSではWebhookを設定できるので、記事公開タイミングでのデータ更新処理を簡単にフックすることができます。
今回は、MicroCMSの管理画面から、GithubのWebhookを設定して、記事公開タイミングでGithub Actionsのワークフローでデータ更新処理&CloudRunへのデプロイを実行するようにしました。
開発してみての感想
PHPでもMicroCMSいけるやん!
って思いました(笑)
私自身、普段使っている技術が非常にレガシーなものになるので、最近のJamstackやフロントフレームワークを使いこなせないというのもあるのですが、成熟した言語であるPHPを利用すると、学習コスト低めでサクッとページが作成できたりします。
なので、MicroCMSなどのヘッドレスCMSを利用する場合、PHPという選択肢は全然アリだと思いました。
開発内容については、結構無理やりな実装方法ではあったかなーと思うのですが、みなさんあまり利用しないであろう、LatteやCycleORMを紹介できたので、開発としては満足です。
今回説明の中に入れたソースコードの内容についてですが、部分的な内容だったので非常にわかりにくかったと思います(申し訳ない)、、、興味のある方は是非リポジトリを見てください。
ありがとうございました。
Discussion