カテゴリー
Linux

【Laravel 5.5】WordPress の DB を使って管理者の投稿一覧ページ作りを通してお勉強

やりたいこと

  • WordPress の データベース構造をそのまま使って、管理者の投稿一覧ページを作りたい。 Laravel 5.5 を使いたい。

学んだことまとめ

WordPress

Laravel 5.5

投稿だけが欲しい

これはつまり、 wp_posts.post_type の値が post のレコードのみが欲しい、ということです。データベース構造 – WordPress Codex 日本語版 を見ると、取りうる値は次です。

投稿種別

  • ‘post’: 投稿
  • ‘page’: ページ
  • ‘attachment’: 添付ファイル
  • ‘revision’: 改訂履歴・自動保存

attachment と revision が謎です。そのまんまの意味だろうと言われればその通りですけれども、 post 、 page 、どちらの attatchment (revision) か? と問われれば今の私には分かりません。よって、謎です。今は置いておきましょう。

追記: 後でちょっと探してみましたら、投稿タイプのページがありました。

where で絞り込む

簡単に実現するために、モデルの ローカルスコープ を作りました。

"app/Models/Wp/Post.php" に次を追加し、

    /**
     * 投稿種別が投稿のレコードに限定するクエリスコープです。
     * @param Builder $query
     * @return Builder
     */
    public function scopePostTypePost(Builder $query): Builder
    {
        return $query->where('post_type', 'post');
    }

次のように使います。

# php artisan tinker
Psy Shell v0.9.9 (PHP 7.3.3 — cli) by Justin Hileman
>>> App\Models\Wp\Post::select('ID', 'post_title', 'post_status', 'post_type')->postTypePost()->get();
=> Illuminate\Database\Eloquent\Collection {#3043
     all: [
       App\Models\Wp\Post {#3042
         ID: 4,
         post_title: "自動下書き",
         post_status: "auto-draft",
         post_type: "post",
       },
       App\Models\Wp\Post {#3041
         ID: 1,
         post_title: "Hello world!",
         post_status: "publish",
         post_type: "post",
       },
     ],
   }
>>>

専用のモデルを作る

Post モデルを継承したモデルを用意してみました。

  • static::addGlobalScope メソッドの第1引数の名前は、ユニークにすること。同じ名前が存在すると framework/HasGlobalScopes.php at 5.5 · laravel/framework を読む限り、上書きすることになるはずで、良からぬことが起きると予想される。
<?php

namespace App\Models\Wp\PostType;

use App\Models\Wp\Post as WpPost;
use Illuminate\Database\Eloquent\Builder;

class Post extends WpPost
{
    /**
     * モデルの「初期起動」メソッド
     *
     * @return void
     */
    protected static function boot()
    {
        parent::boot();

        static::addGlobalScope('postTypePost', function (Builder $builder) {
            $builder->where('post_type', 'post');
        });
    }
}

次のように使えました。いい感じです。ただ、本当に使えるのかどうかはよく分かりません。 Post モデルと PostType¥Post モデルと、併用して進めてみたいと思います。

# php artisan tinker
Psy Shell v0.9.9 (PHP 7.3.3 — cli) by Justin Hileman
>>> App\Models\Wp\PostType\Post::select('ID', 'post_title', 'post_status', 'post_type')->get();
=> Illuminate\Database\Eloquent\Collection {#3057
     all: [
       App\Models\Wp\PostType\Post {#3058
         ID: 4,
         post_title: "自動下書き",
         post_status: "auto-draft",
         post_type: "post",
       },
       App\Models\Wp\PostType\Post {#3059
         ID: 1,
         post_title: "Hello world!",
         post_status: "publish",
         post_type: "post",
       },
     ],
   }
>>>

管理画面の投稿一覧に表示すべき投稿

post_status が何のレコードを投稿一覧に表示するべきか? という問いとなります。 上述の PostType\Post の結果と、 WordPress 管理画面を見比べてみますと、 publish は表示し、 auto-draft は表示しない、ということが少なくとも分かります。

そもそも、 post_status の取りうる値は何でしょうか? 本家ドキュメントがありました♪。

これを見ると、 8 つです。投稿一覧に表示するのは、このうち 6 つです。

  • publish: 公開。パスワード保護、も含む。投稿一覧に表示する。
  • future: 予約。投稿一覧に表示する。
  • draft: 下書き。投稿一覧に表示する。
  • pending: 承認待ち。投稿一覧に表示する。
  • private: 非公開。投稿一覧に表示する。
  • trash: ゴミ箱。投稿一覧に表示する。
  • auto-draft: 自動保存。投稿一覧に表示しない。
  • inherit: 継承。投稿一覧に表示しない。

コードは次のようになりました。

$ git diff app/Models/Wp/Post.php
diff --git a/app/Models/Wp/Post.php b/app/Models/Wp/Post.php
index 6b931f2..be667f5 100644
--- a/app/Models/Wp/Post.php
+++ b/app/Models/Wp/Post.php
@@ -74,4 +74,22 @@ class Post extends Model
     {
         return $query->where('post_type', 'post');
     }
+
+    /**
+     * 投稿ステータスが管理画面の投稿一覧の "すべて" に表示するレコードに限定するクエリスコープです。
+     * @param Builder $query
+     * @return Builder
+     */
+    public function scopePostStatusAll(Builder $query): Builder
+    {
+        return $query->where(function ($query) {
+            $query
+            ->where('post_status', 'publish')
+            ->orWhere('post_status', 'future')
+            ->orWhere('post_status', 'draft')
+            ->orWhere('post_status', 'pending')
+            ->orWhere('post_status', 'private')
+            ->orWhere('post_status', 'trash');
+        });
+    }
 }
$

app/Models/Wp/Post.php に書きましたけれども、これを継承した App\Models\Wp\PostType\Post で次のように使えました。

# php artisan tinker
Psy Shell v0.9.9 (PHP 7.3.3 — cli) by Justin Hileman
>>> App\Models\Wp\PostType\Post::select('ID', 'post_title', 'post_status', 'post_type', 'post_parent')->orderBy('ID', 'asc')->postStatusAll()->toSql();
=> "select `ID`, `post_title`, `post_status`, `post_type`, `post_parent` from `wp_posts` where (`post_status` = ? or `post_status` = ? or `post_status` = ? or `post_status` = ? or `post_status` = ? or `post_status` = ?) and `post_type` = ? order by `ID` asc"
>>>

コントローラーを作成

php artisan make:controller Admin/PostController --resource --model=Models/Wp/Post

今回は、管理者向けの投稿一覧ページを作りますので、自動生成されたコントローラーの index メソッドを作り込んできます。

といっても、継承して定義したモデルと、モデルで定義したスコープを繋げるだけです。短く、意味がわかりやすいようにし、コントローラーを薄くします。

$ git diff app/Http/Controllers/Admin/PostController.php
diff --git a/app/Http/Controllers/Admin/PostController.php b/app/Http/Controllers/Admin/PostController.php
index 35ac414..4e5ae8e 100644
--- a/app/Http/Controllers/Admin/PostController.php
+++ b/app/Http/Controllers/Admin/PostController.php
@@ -3,6 +3,7 @@
 namespace App\Http\Controllers\Admin;

 use App\Models\Wp\Post;
+use App\Models\Wp\PostType\Post as PostTypePost;
 use Illuminate\Http\Request;
 use App\Http\Controllers\Controller;

@@ -15,7 +16,9 @@ class PostController extends Controller
      */
     public function index()
     {
-        //
+        return view('admin.posts.index', [
+            'posts' => PostTypePost::postStatusAll()->get(),
+        ]);
     }

     /**
$

次に、ルーティングです。どの URL を入力した時にこの PostController@index が実行されるようにするかを定義します。

これはちょっと複雑になってしまいました。

  • 管理者ページなので、 URL は admin/posts にしたい。管理者ページは他にも多く作られるため、 Route::prefix で囲む。
  • admin プレフィックスを URL につける場合、 対応するコントローラーの namespace は Admin\XxxController となるため、 Route::namespace で囲む。
  • 典型的な「CRUD」ルートをコントローラへ割り付けたいので、 Route::resource を使う。

routes/web.php

Route::prefix('admin')->group(function () {
    Route::namespace('Admin')->group(function () {
        Route::resources([
            'posts' => 'PostController'
        ]);
    });
});

リソースコントローラーについては、対応するアクションがコントローラーに無ければ意味がないです。 リソースコントローラにより処理されるアクション コントローラ 5.5 Laravel を元にした表です。今回は、次のアクションとなります。

動詞 URI アクション ルート名 内容
GET /posts index posts.index 一覧ページを表示する
GET /posts/create create posts.create 作成ページを表示する
POST /posts store posts.store 入力内容を元に作成する
GET /posts/{photo} show posts.show 詳細ページを表示する
GET /posts/{photo}/edit edit posts.edit 更新ページを表示する
PUT/PATCH /posts/{photo} update posts.update 入力内容を元に更新する
DELETE /posts/{photo} destroy posts.destroy 削除する

php artisan make:controller 時に --resource オプションを付けましたけれども、これを付けることによって Route::resources に対応するアクションを過不足なく付けてくれます。便利です。

ビューを作成

とりあえず固定文字列としていますけれども、 カテゴリーとタグを DB から取得して動的に表示するのが次の作業となりそうです♪

<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="stylesheet" href="{{ asset('css/app.css') }}">

    <title>投稿 - WorldPlace</title>
  </head>
  <body>
    <h1>投稿</h1>

    <table class="table">
      <thead>
        <tr>
          <th scope="col">#</th>
          <th scope="col">タイトル</th>
          <th scope="col">作成者</th>
          <th scope="col">カテゴリー</th>
          <th scope="col">タグ</th>
          <th scope="col">コメント</th>
          <th scope="col">日付</th>
        </tr>
      </thead>
      <tbody>
        @foreach ($posts as $post)
        <tr>
          <th scope="row">{{ $post->ID }}</th>
          <td>{{ $post->post_title }}</td>
          <td>{{ $post->post_author }}</td>
          <td>categories</td>
          <td>tags</td>
          <td>{{ $post->comment_count }}</td>
          <td>{{ $post->post_date }}</td>
        </tr>
        @endforeach
      </tbody>
    </table>

    <script src="{{ asset('js/app.js') }}"></script>
  </body>
</html>

できたもの

https://localhost/admin/posts

Laravel 5.5 を使って WordPress の wp_posts テーブルから管理者の投稿一覧ページを作った

おわりに

Laravel 5.5 に慣れ親しんでいる状態です。

ですけれども、それをアウトプットするのをサボっています><。別のものを触り、また戻ってきた時に、なにもかも忘れていると思いますので、五月雨でまとまっていなくても良いので投稿いたしました。

以上です。

コメントを残す