Site icon Tips Note by TAM

WordPress で HTML とデータ取得ロジックを分離させる方法

はじめに

WordPress サイト開発では、HTML の記述(プレゼンテーション)と、データ取得ロジックが1枚のテンプレートに混在してしまいがちです。

最近はプレゼンテーションとデータ処理の分離を意識してプログラミングすることが多いので、 WordPress サイト開発にも適用できないか試してみました。

実際どんな感じでコーディングしたのかご紹介いたします。

ディレクトリ構成

以下のような形でディレクトリを作成しています。

テーマディレクトリ/
  └ src /
    └ entities /
      構造体クラス群
    └ repository /
      データ取得クラス群

entities ディレクトリ

WordPress から取得したデータの構造体です。

ここのクラスは基本的に画面表示に必要になるデータを構造体のクラスとして定義します。

例えば、固定ページのタイトルや本文、公開日などです。

出力の際のデータ操作ロジックもここのクラスで実装します。

repository ディレクトリ

WordPress からデータを取得するロジックを置く場所です。

WP_Query を使ってデータ取得ロジックを作成し、リソース別にクラスにしてまとめます。

repository に設置するクラスはインターフェースを利用せずに具象クラスで実装していきます。

entity クラス(構造体)を作成する

画面表示に必要になるデータの構造体クラスを作成していきます。

今回は WordPress デフォルトの固定ページと投稿ページの2種類の構造体を作成します。

他にページが増える場合は都度 entity クラスを追加していきます。

固定ページ用の entity クラス

class StaticFrontPageEntity
{
    private $postId = 0;
    private $title = '';
    private $body = '';
    private $slug = '';
    private $link = '';

    public function __construct(
        int $postId,
        string $title,
        string $body,
        string $slug,
        string $link
    ) {
        $this->postId = $postId;
        $this->title  = $title;
        $this->body   = $body;
        $this->slug   = $slug;
        $this->link   = $link;
    }

    // 以下 getter

    public function getPostId()
    {
        return $this->postId;
    }

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

    public function getBody(): string
    {
        return $this->body;
    }

    public function getSlug(): string
    {
        return $this->slug;
    }

    public function getLink(): string
    {
        return $this->link;
    }
}

投稿ページ用の entity クラス

class DynamicFrontPageEntity
{
    private $postId = 0;
    private $publicAt = '';
    private $title = '';
    private $body = '';
    private $slug = '';
    private $category = '';
    private $link = '';

    public function __construct(
        int $postId,
        string $publicAt,
        string $title,
        string $body,
        string $slug,
        string $link,
        CategoryEntity $category
    ) {
        $this->postId   = $postId;
        $this->publicAt = $publicAt;
        $this->title    = $title;
        $this->body     = $body;
        $this->slug     = $slug;
        $this->link     = $link;
        $this->category = $category;
    }

    // 以下 getter

    public function getPostId()
    {
        return $this->postId;
    }

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

    public function getBody(): string
    {
        return apply_filters('the_content', $this->body);
    }

    public function getSlug(): string
    {
        return $this->slug;
    }

    public function getPublicAt(): string
    {
        return $this->publicAt;
    }

    public function getCategory()
    {
        return $this->category;
    }

    public function getLink(): string
    {
        return $this->link;
    }
}

カテゴリー用の entity クラス

投稿ページにはカテゴリーも付けたいのでカテゴリー用の entity も作成します。

class CategoryEntity
{
    private $id = 0;
    private $name = '';
    private $slug = '';
    private $permalink = '';

    public function __construct(
        int $id,
        string $name,
        string $slug,
        string $permalink
    ) {
        $this->id        = $id;
        $this->name      = $name;
        $this->slug      = $slug;
        $this->permalink = $permalink;
    }

    // 以下 getter

    public function getName(): string
    {
        return $this->name;
    }

    public function getSlug(): string
    {
        return $this->slug;
    }

    public function getPermalink(): string
    {
        return $this->permalink;
    }

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

repository クラス(データ取得クラス)を作成する

WordPress では have_posts() や the_post() などを使って記事内容を出力することができます。

実装方法にもよりますが、多くは HTML タグ内に上記メソッドを利用して画面表示ロジックを実装していると思います。

そうすると HTML タグ内に表示の際のロジックを書いてしまい、コードが複雑になってしまいます。

そのデータ取得部分を WordPress が持っている WP_Query クラスを利用して repository クラスにまとめます。

最終的には repository からメソッドを実行すると、各 entity クラスが戻り値として戻ってくるように実装していきます。

そうすることで、画面表示の際のロジックを entity クラスに閉じ込めることができ、 HTML タグ部分に複雑なコードを書かないようにすることができます。

固定ページ用の repository クラス

class StaticFrontPageRepository
{
    // 特定の記事を取得する
    public function findById(int $postId): StaticFrontPageEntity
    {
        $postData = new WP_Query([
            'page_id'     => $postId,
            'post_status' => 'publish',
        ]);

        return $this->mapToEntity($postData->posts[0]);
    }

    // 作成した entity クラスへマップする
    protected function mapToEntity(WP_Post $query): StaticFrontPageEntity
    {
        return new StaticFrontPageEntity(
            $query->ID,
            $query->post_title,
            $query->post_content,
            $query->post_name,
            get_permalink($query->ID)
        );
    }
}

投稿ページ用の repository クラス

class DynamicFrontPageRepository
{
    // 特定の記事を取得する
    public function findById(int $postId, WP_Term $categoryObj): DynamicFrontPageEntity
    {
        $postData = new WP_Query([
            'p'           => $postId,
            'post_status' => 'publish'
        ]);

        $categoryEntity = $this->mapToCategoryEntity($categoryObj);

        return $this->mapToEntity($postData->posts[0], $categoryEntity);
    }

    /**
     * 特定のカテゴリーに所属した記事を取得する
     *
     * @param int $categoryId
     * @return DynamicFrontPageEntity[]
     */
    public function findByCategoryId(int $categoryId): array
    {
        $postData = new WP_Query([
            'post_type'    => 'post',
            'post_status'  => 'publish',
            'category__in' => $categoryId
        ]);

        return array_map(function ($post) {
            /** @var WP_Term $categoryObj */
            $categoryObj    = get_the_category($post->ID);
            $categoryEntity = $this->mapToCategoryEntity($categoryObj[0]);

            return $this->mapToEntity($post, $categoryEntity);
        }, $postData->posts);
    }

    // 作成した entity クラスへマップする
    private function mapToEntity(WP_Post $query, CategoryEntity $categoryEntity): DynamicFrontPageEntity
    {
        $dateTimeObj = new DateTime($query->post_date);
        $publishAt   = $dateTimeObj->format('Y.m.d');

        return new DynamicFrontPageEntity(
            $query->ID,
            $publishAt,
            $query->post_title,
            $query->post_content,
            $query->post_name,
            get_permalink($query->ID),
            $categoryEntity
        );
    }

    // 作成した entity クラスへマップする
    private function mapToCategoryEntity(WP_Term $categoryObj): CategoryEntity
    {
        return new CategoryEntity(
            $categoryObj->term_id,
            $categoryObj->name,
            $categoryObj->slug,
            get_category_link($categoryObj->term_id)
        );
    }
}

テンプレートファイルに組み込む

テンプレートファイルへ組み込む際は取得したいリソースのリポジトリークラスのメソッドを実行します。

固定ページテンプレート(page.php)

<?php

use src\repository\StaticFrontPageRepository;

$staticFrontPageRepository = new StaticFrontPageRepository();
$staticFrontPageEntity     = $staticFrontPageRepository->findById(get_the_ID());
?>

<?php get_header(); ?>

<h1>固定ページサンプル</h1>

<div>タイトル: <?= $staticFrontPageEntity->getTitle() ?></div>
<div>リンク: <?= $staticFrontPageEntity->getLink() ?></div>
<div>本文: <?= $staticFrontPageEntity->getBody() ?></div>
<div>スラッグ: <?= $staticFrontPageEntity->getSlug() ?></div>
<div>記事ID: <?= $staticFrontPageEntity->getPostId() ?></div>

<?php get_footer(); ?>

投稿ページテンプレート(single.php)

<?php

use src\repository\DynamicFrontPageRepository;

$categoryObj                = get_the_category();
$dynamicFrontPageRepository = new DynamicFrontPageRepository();
$dynamicFrontPageEntity   = $dynamicFrontPageRepository->findById(
    get_the_ID(),
    $categoryObj[0]
);
?>

<?php get_header(); ?>

<h1>投稿ページサンプル</h1>

<div>タイトル: <?= $dynamicFrontPageEntity->getTitle() ?></div>
<div>リンク: <?= $dynamicFrontPageEntity->getLink() ?></div>
<div>本文: <?= $dynamicFrontPageEntity->getBody() ?></div>
<div>スラッグ: <?= $dynamicFrontPageEntity->getSlug() ?></div>
<div>記事ID: <?= $dynamicFrontPageEntity->getPostId() ?></div>
<div>公開日: <?= $dynamicFrontPageEntity->getPublicAt() ?></div>
<div>カテゴリーID: <?= $dynamicFrontPageEntity->getCategory()->getId() ?></div>
<div>カテゴリースラッグ: <?= $dynamicFrontPageEntity->getCategory()->getSlug() ?></div>
<div>カテゴリー名: <?= $dynamicFrontPageEntity->getCategory()->getName() ?></div>
<div>カテゴリーリンク: <?= $dynamicFrontPageEntity->getCategory()->getPermalink() ?></div>

<?php get_footer(); ?>

カテゴリー一覧ページテンプレート(category.php)

<?php
/** @var WP_Term $categoryObj */

use src\repository\DynamicFrontPageRepository;

$categoryObj                = get_category(get_query_var('cat'));
$dynamicFrontPageRepository = new DynamicFrontPageRepository();
$dynamicFrontPageEntities = $dynamicFrontPageRepository->findByCategoryId($categoryObj->term_id);
?>

<?php get_header(); ?>

<h1>カテゴリー一覧ページ</h1>

<ul>
  <?php foreach($dynamicFrontPageEntities as $entity): ?>
  <li>
    <a href="<?= $entity->getLink() ?>"><?= $entity->getTitle() ?></a>
  </li>
  <?php endforeach; ?>
</ul>

<?php get_footer(); ?>

HTML へ出力する際はこの様な形でシンプルに記載します。

また、出力用に値を調整したい場合は entity クラスの getter 部分に処理を入れるか別の getter メソッドを作成するなどをして HTML 出力部分に PHP のロジックが漏れないようにすることができます。

一度このクラスを作ってしまえば使い回しもできるので、そういった点でも便利ではないかなと思います。

最後に

今回の例では固定ページ用と投稿ページ用のみ作成したので、逆にコードが増えて面倒くさいだけじゃないかと思うかもしれませんが、カスタム投稿タイプなどを使って項目数が増えていったり、複雑な要件のサイト構築になる場合には、これである程度コードが見やすくなるのではないかと思います。

できるだけロジック部分を HTML 部分に持ち込まないようにするのと、ロジックは細かくクラスに分け、タイプヒンティングを利用するだけでもある程度の可読性は担保されるかなと思います。

見やすいコードを書いていきたいですね。

最後に雑ではありますが、今回のソースコードを GitHub にあげておりますのでよろしければご確認ください。