GravとFlex Objectsについて
Gravとは、PHPのSymfony製のflat-file CMSです。
MySQLなどのRDBを使わず、マークダウンファイルだけでサイトが作れます。
その気軽さから、大変評価の高いCMSのひとつです。
なお、静的サイトジェネレータではなく、動的サイトであるため、記事の予約投稿など、動的処理が可能です。
一方、RDBを使わないため、1000ページを超えるようなサイトだと、実用に向かなくなるそうです。
このデメリットを克服するため(だと思うのですが)、json やyaml でデータ管理できるようにしたのが、Flex Objects というプラグインのようです。
作者によれば、Flex Objects を使えば、5万ページ以上でも使える そうです。
Flex Objects は、Grav 1.7から追加されました。
今回は、このFlex Objects を使って、ミニブログ(microblog)を作りたいと思います。
環境
Grav 1.7.46
Flex Objects v1.3.6
参考サイト
Gravについて:
Flex Objectsについて:
Flex Objectでblogを作っている例:
プラグインをつくる
ミニブログ用の独自プラグインをつくります。
今回は、
- 独自プラグイン名を
mblo
にし、 - Flex Directory名を
microblog
にしました
なお、Flex Directoryというのは、Flex Objectsを使うときの、データのまとまりのようなイメージです。
手順は、公式ドキュメントのとおりです。
プラグインをつくるには、CLIから入力が必要です。
SSH接続していないレンタルサーバーの場合、いったんローカル環境でプラグインを作成してから、FTPなどでアップロードすることになるのではないかと思います。
bin/gpm install devtools
grav-admin bin/plugin devtools new-plugin
これを実行すると、いろいろ聞かれますが、とりあえず、以下2点に気をつける必要があります。
- オプションでflexを選択することと、
- ストレージはfolderを選択すること
※ストレージは、folderではなく、fileを選んでも良いかもしれません。
しかしsimpleを選ぶと、画像のアップロードができない(かもしれない)ので、注意してください。
(厳密に言えば、このあと操作するuse/plugins/【独自プラグイン】/blueprints/flex-objects/【Flex Directory】.yaml
ファイルの、form.fields.images.destinationに、保存先ディレクトリを指定すれば、画像をアップロードできなくはないのかもしれません。
なんとなく、推奨されていなさそうだったので、今回はsimpleをさけました。)
その後、できた独自プラグインのディレクトリにcdして、composer update
してください。
必要な入力フィールドを考える
ミニブログなので、投稿本文は必須です。
将来的にはmarkdownにしたくなるかもしれませんが、難しくなりそうだったので、今回はプレーンなテキストエリアで作ります。
また、投稿日時は、自動入力したいです。
画像も、ツイッター(X)みたいに4枚以内くらいにはアップロードしたいです。
一方、各投稿にタイトルやカテゴリー分けは不要です。
よって、以下のようなフィールドを想定しました。
form:
validation: loose
fields:
log:
type: textarea
label: '投稿'
validate:
required: true
time:
type: datetime
label: '投稿日'
format: 'Y-m-d H:i:s'
data-default@: '\Grav\Plugin\【独自プラグイン名】Plugin::currentTimeDataDefault'
validate:
required: true
images:
type: file
label: '画像'
multiple: true
limit: 4
accept:
- image/*
Gravでは、設定をyamlファイルでするのですが、formの設定については、公式ドキュメントが参考になります。
form.fields.time.data-default@
は、投稿日時の自動入力に必要です。
独自プラグインのPHPファイル(user/plugins/【独自プラグイン名】/【独自プラグイン名】.php
)に、以下のような関数を追加しておきます。
現在時刻を返すだけの関数ですが、data-default@に設定しておくと、デフォルト値として自動入力してくれます。
public static function currentTimeDataDefault(): string
{
return date('Y-m-d H:i:s');
}
blueprintを作る
上記のformを含む設定ファイルを作成します。
このような設定ファイルを、Gravではblueprintと呼んでいます。
設定ファイルは、独自プラグインディレクトリの、blueprints/flex-objects/
フォルダに入っています。
とても長いですが、ほとんどは自動で出力されるものです。
title: 'Microblog'
description: 'Gravでつくるミニブログ'
type: flex-objects
# Flex Configuration
config:
# Administration Configuration
admin:
# Admin router (optional)
router:
path: '/microblog'
# Admin menu (optional)
menu:
list:
route: '/microblog'
title: 'Microblog'
icon: fa-address-card-o
# Authorization to collection admin
authorize: ['admin.microblog.list', 'admin.super']
# Priority -10 .. 10 (highest goes up)
priority: 2
# Admin template type / folder
template: default
# Permissions
permissions:
# Primary permissions
admin.microblog:
type: crudpl
label: 'Microblog'
# List view
list:
title: log
fields:
time:
log:
link: edit
#images:
options:
per_page: 10
order:
by: time
dir: desc
# Edit View
edit:
title:
template: ''
# Preview View
preview:
enabled: false
route:
#template: '/plugins/flex-objects/directory:contacts'
# Data Export
export:
enabled: true
method: 'jsonSerialize'
formatter:
class: 'Grav\Framework\File\Formatter\YamlFormatter'
filename: 'microblog'
# Site Configuration
site:
templates:
collection:
# Lookup for the template layout files for collections of objects
paths:
- 'flex/{TYPE}/collection/{LAYOUT}{EXT}'
object:
# Lookup for the template layout files for objects
paths:
- 'flex/{TYPE}/object/{LAYOUT}{EXT}'
defaults:
# Default template {TYPE}; overridden by filename of this blueprint if template folder exists
type: 'microblog'
# Default template {LAYOUT}; can be overridden in render calls (usually Twig in templates)
layout: default
# Data Configuration
data:
# Object class to be used, allowing custom methods for the object
object: 'Grav\Plugin\Mblo\Flex\Types\Microblog\MicroblogObject'
# Collection class to be used, allowing custom methods for the collections
collection: 'Grav\Plugin\Mblo\Flex\Types\Microblog\MicroblogCollection'
# Index class to be used, works as a quick database-like lookup index
index: 'Grav\Common\Flex\Types\Generic\GenericIndex'
storage:
# Storage class, use single file storage (does not support images and assets)
class: 'Grav\Framework\Flex\Storage\FolderStorage'
options:
formatter:
# File formatter class, in this case the file is stored in markdown
class: 'Grav\Framework\File\Formatter\JsonFormatter'
# JSON file where all the objects will be stored
folder: user-data://microblog
ordering:
time: DESC
search:
# Search options
options:
contains: 1
# Fields to be searched
fields:
form:
validation: loose
fields:
log:
type: textarea
label: '投稿'
validate:
required: true
time:
type: datetime
label: '投稿日'
format: 'Y-m-d H:i:s'
data-default@: '\Grav\Plugin\mbloPlugin::currentTimeDataDefault'
validate:
required: true
images:
type: file
label: '画像'
multiple: true
limit: 4
accept:
- image/*
flex-objectsのyaml設定については、公式ドキュメントが参考になります。
公式ドキュメントによると、細かい設定もできるようです。
管理画面で確認してみる
この時点で、管理画面から操作できるようになります。
左側のメニューリストに、カードのようなアイコンが増えているはずなので、そこから操作できます。
初期設定のままだと、日付表示が、外国の表示形式なので、日本で馴染み深い年-月-日 時間:分
にします。
まず、user/plugins/【独自プラグイン名】/【独自プラグイン名】.php
を開きます。
この中の、getSubscribedEvents()
という関数の戻り値が配列になっているので、この配列の中に、以下を追記します。
'onTwigTemplatePaths' => ['onTwigTemplatePaths', 1]
そして、onTwigTemplatePaths()
関数を作ります。
public function onTwigTemplatePaths() : void
{
$this->grav['twig']->twig_paths[] = __DIR__ . '/templates';
if ($this->isAdmin())
{
$this->grav['twig']->twig_paths[] = __DIR__ . '/admin/templates';
}
}
なお、/templates
フォルダは、後ほど作ります。
次に、user/plugins/【独自プラグイン】/admin/templates/forms/fields/datetime/edit_list.html.twig
ファイルを作ります。
このtwigファイルには、以下を記入します。
{{ value ? value|date(Y-m-d G:i)|e }}
おそらくこれで、adminプラグインのtwigファイルが上書きされ、日付が日本の表示になるはずです。
ミニブログ用のページを準備する
管理画面から、ページを追加します。
ページの追加方法は、記事を書くなどが参考になります。
frontmatterを書くのですが、Flex Objectsの場合、以下のように書きます。
---
title: home
flex:
directory: microblog
collection:
title: '{{ directory.title }}'
layout: default
object:
layout: default
object:
title: 'object'
layout: default
---
# flex microblogging
frontmatterの書き方は、公式ドキュメントを参考にしてください。
Twigを用意する
先ほど書いたfrontmatterで、layoutをdefaultにしていましたが、このdefaultのレイアウトを作成します。
collection(記事一覧)用のdefaultレイアウトと、object(個別の記事)用のdefaultレイアウトの、2種類あります。
作成場所は、user/plugins/【独自プラグイン名】/templates/flex/【ディレクトリ名】/collection/defaul.html.twig
と、
user/plugins/【独自プラグイン名】/templates/flex/【ディレクトリ名】/object/defaul.html.twig
の2ヶ所です。
Twigでは、いろいろできそうですが、さしあたり日付降順表示となるように、以下のように書きました。
<div style="max-width:30rem;margin-inline:auto;">
{%- for object in collection -%}
<div style="margin-block:3rem;padding:1rem;border:thin solid #aaa;">
{%- render object layout: object_layout with object_context -%}
<a href="./id:{{ object.getStorageKey() }}">個別記事ページへ</a>
</div>
{%- endfor -%}
</div>
<div>
<p style="font-size:1.125rem;">{{ object.log|nl2br }}</p>
{%- if object.images -%}
<div style="display:flex;flex-wrap:nowrap;justify-content:center;gap:5px;">
{% for image_data in object.images %}
{% set image = object.media.all[image_data.name] %}
<div style="max-height:200px;">
{{ image.cropResize(200, 100).html()|raw }}
</div>
{% endfor %}
</div>
{%- endif -%}
<p style="border-top:thin solid #aaa;color:#777;font-size:.95;text-align:right;margin:0;">
{{ object.time }}
</p>
</div>
この状態で、先ほど作成したページにアクセスすると、ミニブログが一覧表示されるはずです。
感想
ここまでで、とりあえず、最低限のものはできたと思います。
が、リンクを張ったり、ページネーションを設定したりと、まだまだ不完全です。
また、Generic indexというClassをextendすることで、データを探すときの索引が作れるようです。
データが多くなれば、仕方ないことかもしれませんが、調整しないといけないことがどんどん増えていきます。
他のサイトでも指摘されていることですが、Gravは、基本的には、1000ページ以下の、小規模なサイトの運用に向いている気がします。
どうしてもGravで大量のデータが扱いたい場合のみ、Flex Objectsを使うのがよいと思いました。