CakePHP 4 で Parsedown を使い Markdown をパースして HTML に変換する方法

はじめに

Markdown は文章の構造を記述するためのマークアップ言語です。
例えば # 見出し1 は HTML での <h1>見出し1</h1>に相当します。

Qiita や Backlog、GitHub など多くのサービスで採用されていますね。

CMS やブログに Markdown を導入すれば、文章作成速度や保守性の向上が期待できます。今日は CakePHP 4 で Markdown をパースし HTML に変換するサンプルをいくつかご紹介します。

今回は Markdown の HTML 変換に Parsedown ライブラリを使いました。

CakePHP
4.0.3
Parsedown
1.7.4
目次
  1. Parsedown をインストール
  2. コントローラに直接実装(非推奨)
  3. Component として実装
  4. Helper として実装
  5. Trait(Entity)として実装
  6. おわりに

1. インストール

Parsedown の README に沿って Composer を使ってインストールします。

> cd \path\to\cakephp4
> composer require erusev/parsedown

2. コントローラに直接実装(非推奨)

まずはコントローラに直接実装してみます。

/src/Controller/SampleController.php
<?php
declare(strict_types=1);

namespace App\Controller;

use Parsedown; // ← 追加

class SampleController extends AppController
{
  public function index()
  {
    $bodyMd = "# こんにちは、世界!\n"
            . "## Hello, world\n"
            . "これはサンプルです\n";
    $Parsedown = new Parsedown();
    $body = $Parsedown->text($bodyMd);
    $this->set(compact('body'));
  }
}
/templates/Sample/index.php
<?= $body ?>

Parsedown クラスの text() 関数を使うことで Markdown から HTML に変換できます。

とても簡単に使えますね。しかし、見出しにも書きましたが、この実装方法はあまりオススメしません。

Markdown を HTML に変換するこの機能を、複数のアクションで使用する場合を想定します。何らかの事情でライブラリを Parsedown から別のものに変更する可能性がありますが、その際に複数個所で使用していると修正が手間になります。

Markdown 処理用の独自クラスを作り、そこで Markdown ライブラリを呼び出して実行するラッパー関数を実装すれば、保守性が高くなります。

クラスを作る場所をどこにするかですが、CakePHP では ComponentHelperTrait (entity) を使うのが良さそうです。それぞれの場合について、サンプルをご紹介します。

3. Component として実装

Component はコントローラで使う機能を実装するところです。今回の例では、下記のように実装することができます。

/src/Controller/Component/MarkdownComponent.php
<?php
declare(strict_types=1);

namespace App\Controller\Component;

use Cake\Controller\Component;
use Parsedown;

class MarkdownComponent extends Component
{
  public function md2html($str)
  {
    $Parsedown = new Parsedown();

    return $Parsedown->text($str);
  }
}
/src/Controller/SampleController.php
<?php
declare(strict_types=1);

namespace App\Controller;

// use Parsedown; は削除

class SampleController extends AppController
{
  public function index()
  {
    $bodyMd = "# こんにちは、世界!\n"
            . "## Hello, world\n"
            . "これはサンプルです\n";
    // ▼ 変更
    $this->loadComponent('Markdown');
    $body = $this->Markdown->md2html($bodyMd);
    $this->set(compact('body'));
  }
}

こうすれば、ライブラリ変更のときには MarkdownComponent.php を直すだけで済みます。

4. Helper として実装

Markdown → HTML の変換は表示形式に関する処理なので、View 側で制御しても良さそうです。Helper は View から呼び出す機能を実装するところです。

/src/view/Helper/MarkdownHelper.php
<?php
declare(strict_types=1);

namespace App\View\Helper;

use Cake\View\Helper;
use Parsedown;

class MarkdownHelper extends Helper
{
  public function md2html($str)
  {
    $Parsedown = new Parsedown();

    return $Parsedown->text($str);
  }
}
/src/Controller/SampleController.php
<?php
declare(strict_types=1);

namespace App\Controller;

class SampleController extends AppController
{
  public function index()
  {
    $bodyMd = "# こんにちは、世界!\n"
            . "## Hello, world\n"
            . "これはサンプルです\n";
    $this->set(compact('bodyMd')); // ← 変数名 bodyMd です
  }
}
/templates/Sample/index.php
<?= $this->Markdown->md2html($bodyMd) ?>

5. Trait (entity) として実装

データベースを用いる場合は entity の仮想プロパティを使うこともできます。

各エンティティで使う機能は trait として実装します。

サンプルコードは以下の通りです。

CREATE TABLE `articles` (
  `id` int(11) NOT NULL,
  `body` text NOT NULL,
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

ALTER TABLE `articles`
  ADD PRIMARY KEY (`id`),
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

INSERT INTO `articles`
  (`id`, `body`, `created`, `modified`)
VALUES
  ('1', '# こんにちは、世界!\n## Hello, world\nこれはサンプルです', NOW(), NOW());
/src/Controller/SampleController.php
<?php
declare(strict_types=1);

namespace App\Controller;

class SampleController extends AppController
{
  public function index()
  {
    $this->loadModel('Articles');
    $article = $this->Articles->find()->first();
    $this->set(compact('article'));
  }
}
/src/Model/Entity/MarkdownTrait.php
<?php
namespace App\Model\Entity;

use Parsedown;

trait MarkdownTrait
{
  public function md2html($str)
  {
    $parsedown = new Parsedown;

    return $parsedown->text($str);
  }
}
/src/Model/Entity/Article.php
class Article extends Entity
{
  use MarkdownTrait; // ←追加
  ...
  // ▼追加
  protected function _getBodyHtml()
  {
    return $this->md2html($this->body);
  }
}
/templates/Sample/index.php
<?= $article->body_html ?>

<pre><?= $article->body ?></pre>

6. おわりに

今日は Parsedown を使って Markdown から HTML に変化する方法をご紹介しました。非常に簡単に実装できますが、やはりその方法と場所が重要だと考えています。

実装方法と場所を工夫することで保守性を向上させることができます。

どうしても判断がつかない場合は、チームリーダーやメンバーに相談するか、個人プロジェクトなら自分が保守しやすい方法を採用すればいいと思います。

十分な時間が取れないプロジェクトも多々あると思いますが、こういったところ考えながら進めていくと、徐々に良いコードが書けるようになると思っています。