このエントリーをはてなブックマークに追加

こんにちは。澤井です! 私はカルテット入社前にWordPressを使ったサイト制作をしていました。 入社後の現在でも保守や構築を作業する事があり、WordPressは私にとってなじみの深いCMSです。

今回は、そんな私にとってなじみの深いWordPressの基本機能である、パーマリンクについてすこし詳しく書こうと思います。

パーマリンク

WordPressの公式ドキュメントでは、パーマリンクを以下のように解説しています。

パーマリンクとは、ブログの個々の投稿、カテゴリーなどの投稿一覧ページへの恒久的 (半永久的) な URL です。

主に使われるパーマリンクは2種類あります。

  • Uglyパーマリンク(デフォルト)
  • Prettyパーマリンク

パーマリンクは、WordPress管理画面 > 設定 > パーマリンク設定で設定します。

パーマリンク設定画面

本記事では、Uglyパーマリンクの表示の流れを確認して、その後にPrettyパーマリンクUglyパーマリンクに変換するルール(リライトルール)についてみていきたいと思います。

Uglyパーマリンク

WordPressをインストールした直後のURL構造は、https://example.com/?p=123のように、クエリ文字列を使用します。 この形式は、Uglyパーマリンクと呼びます。

WordPressは、クエリ文字列にマッチする投稿を表示します。 例えば、スラッグがcategory-sampleのカテゴリーの中で、投稿IDが1の投稿を表示するURLは以下のようになります。

https://example.com?p=1&category-name=category-sample&page=

※ 実際には、https://example.com?p=1で同じ投稿を表示できますが、 説明を分かりやすくするために、category-name=category-sample&page=も加えています。

上記のpcategory-namepageなどを、パブリッククエリとよびます。 パブリッククエリは、WordPressがデータベースに問い合わせする際の条件として使われます。 WordPressで使用可能なパブリッククエリは、WPクラス(wp-includes/class-wp.php)で定義されています。

WordPressは、パブリッククエリをもとに問い合わせ条件を作成し、SQLを実行してデータベースから表示すべき投稿を取得します。

https://example.com?p=1&category-name=category-sample&page=の投稿取得条件は、以下のようになります(投稿取得条件は、WP_Query::query_varsプロパティで確認できます)。

Array
(
    [p] => 1
    [page] => 
    [category_name] => category-sample
)

pageクエリは、今回の記事では重要でないので、説明しませんが、詳しく知りたい方は以下を参考にしてください。

注: ‘page’ クエリ変数には、コンテンツに クイックタグが含まれページ送りが設定されている個別投稿または固定ページのページ数が含まれます。

関数リファレンス/get query var - WordPress Codex 日本語版

Prettyパーマリンク

Prettyパーマリンクは、ユーザーフレンドリーで見た目の良いリンク構造を提供します。 実際の運用では、Uglyパーマリンクを使うことは少なく、Prettyパーマリンクを使用することが多いと思います。

以下、本記事の主題であるPrettyパーマリンクからUglyパーマリンクへ変換するリライトルールについて記載します。

今回は、Prettyパーマリンクを以下のように設定してみます。

カスタム構造 https://example.com/%category%/%post_id%

上記のようにパーマリンクを設定をした場合、https://example.com?p=1&category-name=category-sample&page=で表示される投稿は、以下URLで取得できるようになります。

https://example.com/category-sample/1

WP_Query::query_varsを確認すると、投稿取得条件は、 Uglyパーマリンクと同じになっています。

Array
(
    [p] => 1
    [page] => 
    [category_name] => category-sample
)

https://example.com/category-sample/1https://example.com?p=1&category-name=category-sample&page=へ変換するルールをリライトルールとよびます。

リライトルールは、wp_options.option_namerewrite_rulesレコードに格納されています。 WordPressは初期化時に、WP_Rewrite::rulesプロパティにリライトルールを保持します。
(WP_Rewriteオブジェクトはグローバル変数$wp_rewriteとしてアクセスできます)。

パーマリンク構造を、https://example.com/%category%/%post_id%に設定したときのWP_Rewrite::rulesプロパティを確認してみます。 WP_Rewrite::rulesは、正規表現とマッチ後の置き換え方法を表します。

正規表現でキャプチャされた部分を$match配列に順番に適用していきます。 上から順にマッチを試み、最初にマッチしたものを変換ルールとして採用します。

WP_Rewrite Object
(
    [permalink_structure] => /%category%/%post_id%
    // ...
    [rules] => Array
        (
            [^wp-json/?$] => index.php?rest_route=/
            // ... 
            [(.+?)/([0-9]+)(?:/([0-9]+))?/?$]
                 => index.php?category_name=$matches[1]&p=$matches[2]&page=$matches[3] --- (1)
            // ...
            [(.+?)/?$] => index.php?category_name=$matches[1]
        )
   // ...
)

https://example.com/category-sample/1は、上記(1)にマッチします。

  • 1番目にキャプチャされたcategory-sampleは、$matches[1]で使用されて、category_name=category-sampleになる
  • 2番目にキャプチャされた1は、$matches[2]で使用されて、p=1になる
  • 3番目のキャプチャはないので、$matches[3]は空になり、page=になる

リライトルールが適用された結果、Uglyパーマリンクhttps://example.com?p=1&category-name=category-sample&page=と同じクエリ文字になります。 結果、WP_Query::query_varsUglyパーマリンクと同じものになります。

Array
(
    [p] => 1
    [page] => 
    [category_name] => category-sample
)

以上、PrettyパーマリンクからUglyパーマリンクへのリライトルールを見てきました。

注:WordPressはフロントコントローラーindex.phpですべてのリクエストを処理するので、他のページにアクセスした時にindex.phpに処理を移すようWebサーバーを設定しておく必要があります。Webサーバーの設定もリライトと呼ばれますが、WordPressのリライト(Ugly/Pretty)とは全く別の設定箇所になりますのでご注意ください。例えばApacheではmod_rewriteを有効にし.htaccessに設定を記述します。

WordPressは、パーマリンクの設定次第でURL構造が大きく変わります。 思ったとおりのURLで投稿が表示されないといった問題が発生した場合は、一度リライトルールを確認すると良いかもしれません。


このエントリーをはてなブックマークに追加

2020年あけましておめでとうございます!今年もカルテット開発部ブログをよろしくおねがいします!

この記事は Symfony Advent Calendar 2019 19日目の記事です。大幅に遅刻しましたが検証に時間がかかったためなのでご容赦ください m(_ _)m

now.shとは

now.sh とは、サーバーレスで様々な言語のウェブアプリケーションを動かすことができるPaaSです。

公式にサポートされているのはnode.js, Go, pythonですが、サードパーティのビルダーを利用することでPHPも使えます。
※ ただし、 PHPのビルトインウェブサーバー を使うことになるので、本番環境にはおすすめできないかもしれません。試す際は自己責任でお願いします。

Symfony4アプリをGitHub経由でnow.shにデプロイしてWEBアプリケーションとして動かす

ソースコード側の準備

src/Kernel.php に細工しておく

now.shの環境では /var/cache/var/log に書き込めないので、キャッシュ書き込み先とログ書き込み先をnow.shでも書き込める /tmp 等に変えておく必要があります。 どこをどのように変えるのかは https://github.com/juicyfx/now-php/blob/master/examples/framework-symfony-microservice/src/Kernel.php を参考にしてください。

.env じゃないファイルを使う

now.shではデプロイ時に .env という名前のファイルはすべて無視されます。
無視しない設定はありません、必ず削除されます(T_T)

かと言って Symfony4+で .env は必要な環境変数のテンプレートなので、内容が全然なくても .env を置かないでアプリケーションを動かすことはできません(厳密に言うと違うんですが…気になる人はお手元のSymfonyアプリケーションのconfig/bootstrap.phpを読んでみてください)。now.shのデプロイ時に .env を消されないためには、 .env.env じゃない名前に変えます。私は myenv にしました。
.env を変えたら config/bootstrap.php でSymfonyDotEnvによって .env を読み込んでいる箇所を探して、 myenv を読むように変更しておきましょう。

Symfony4のphpunit.xml.distはテスト時のbootstrapとして config/bootstrap.php を読み込んでくれるので、ローカルでの機能テスト等も問題なく動きます。
テスト用の環境変数は .env.test でなく myenv.test、 ローカル動作確認用の環境変数は .env.local でなく myenv.local に記載すればOKです。

デプロイ設定

秘密の環境変数の準備(ローカル)

アプリケーションにはDBのパスワードとか、SECRETとか。レポジトリにコミットしたくない環境変数がありますよね。
そういった秘密の環境変数はローカルPCにnow-cliをインストールしてnow-secretsに登録しておきます。

# xxxという変数名でyyyという値を登録
$ now secrets add xxx yyy

注意しておきたいのは、secret名はユーザー別であってプロジェクト別ではないという点です。複数のWEBアプリをnow.shにデプロイしてそれぞれ違う値を使いたい場合はそれぞれ a-password b-password のように、どのsecretがどのプロジェクト用のものかわかるようにしておくと良いでしょう。

.nowignoreの準備

now.shにデプロイするときに無視するファイル・ディレクトリを指定するファイルです。gitignoreと同じようなものと考えてください。
ローカルから /vendor を含めてデプロイしようとするとビルドが FetchError というエラーでほぼ確実に落ちるので、 .nowignore/vendor は除外しておきましょう。
/vendor なしでデプロイしてもサーバー側で勝手に composer install してくれるようです。

now.jsonの準備

now.json を準備します。 now.jsonはnow.shのためのデプロイ指定を記述するjsonファイルです。プロジェクトのルートディレクトリに置きます。

{
  "version": 2,
  "env": {
      "APP_ENV": "prod",
      "MY_ENV": "@my-env"
  },
  "builds": [
    { "src": "public/index.php", "use": "now-php"}
  ],
  "routes": [
    { "src": "/(.*)", "dest": "public/index.php" }
  ]
}

{ "src": "public/index.php", "use": "now-php"} で指定している now-php というのがPHP用のビルダーです。
公式でなくサードパーティ扱いですが、特にベンダープリフィクスは不要のようです。

now.sh側で composer install するときは require-dev は無視されるようなので、 APP_ENV はかならず prod にしましょう。
now secretsに登録した秘密の環境変数は @ をつけて参照できます。(MY_ENVの定義を見てください)

デプロイ

GitHubにpushするだけです。

安定性

正直自分専用の軽いAPIアプリしか動かしてないので、まだわかりません。
最初に書いたように、PHPのビルトインウェブサーバーを使うことになるので、安定性は保証できないと思っています。


このエントリーをはてなブックマークに追加

Symfonyアドベントカレンダー 23日目の記事です :christmas_tree::crescent_moon:

こんにちは。カルテットコミュニケーションズ でフロントエンドエンジニアをしています、松岡です。

ここ数年はフロントエンド開発に没頭しているため Symfony の知識はほとんどありません…。ですが Symfony4+ で Server-Sent events が使えるらしい!という事を知って思わず飛びついてしまいました。

この記事では Server-Sent events をローカル開発環境で試すまでのステップを紹介させていただきます。

Symfony Gets Real-time Push Capabilities!
https://symfony.com/blog/symfony-gets-real-time-push-capabilities

Server-Sent events とは

平たく言えばバックエンドからフロントエンドに対してプッシュ通知する仕組みです。

データの更新を伴う処理は、画面での保存ボタンのクリックなどをトリガとして、フロントエンドからバックエンドに対して ajax でリクエストするのがよく使われる方法です。

エンティティの保存など、短時間かつ単独のプロセスで処理が完了するものについては 1回の ajax リクエストで完了したかどうかの判定をする事が多いですよね。ですがレポート生成など「いつ終わるか分からない」ものに対して ajax リクエストのレスポンスを待っていると数時間も待機状態になってしまう事があります。

このような場合は、数秒〜数分おきにステータス変更の問い合わせを ajax リクエストで行う ポーリング という手法をよく使っていました。ポーリングには、リクエストの間隔を短くしたり監視対象が増えたりすると ajax リクエストが爆発的に増えてしまうというデメリットがあります。

SSE(Server-Sent events) では、繰り返しの ajax リクエストは不要になり、ポーリングよりずっとスマートに処理を行う事ができます。

検証に使った環境とパッケージ

Symfony のローカルサーバーを立ち上げるまで

まずは Symfony のインストールから始めましょう。

参考サイト
【初心者向け】初めてのSymfony4
Symfony4+ が全く分からないので同僚エンジニアに入門記事を書いて!!と頼んで書いてもらった記事です。めちゃくちゃ参考になりました。

今回の SSE は Symfony 5系ではうまく動作しなかったので、4系 をインストールしました。

$ mkdir symfony-sse && cd $_
$ composer create-project symfony/website-skeleton:^4.4 symfony-sse

ローカル用の環境変数を設定します。

$ cp .env .env.local

# .env.local
# DATABASE_URL=mysql://{USER}:{PASSWORD}@127.0.0.1:3306/sample?serverVersion=5.3

今回は Symfony の Web アプリケーションとしての機能は使用しませんが、正常に動作している事を確認したいのでローカルサーバーをインストールします。

$ composer req server
$ php -S 127.0.0.1:8000 -t public/

bin/console server:start でサーバーは起動するもののブラウザで該当ページが Connecton refused になってしまうので php コマンドを使いました…。

ブラウザで http://127.0.0.1:8000 を開いて「Welcome to Symfony 4.4」が表示されればセットアップ完了です!

Symfony でプッシュ通知をする

バンドルのインストール

Symfony で SSE を実現するのは mercure(メルキュール)という外部のパッケージです。

コンポーネントとして symfony/mercure が公開されていて単体で利用する事もできますが、それをラップし設定を環境変数から読み込んでくれるバンドル symfony/mercure-bundle が便利です。今回はバンドルのほうをインストールします。

$ composer require mercure

インストールが完了すると .env.local に設定が追加されています。プッシュ通知の URL をローカル検証用のものに変更しておきます。

###> symfony/mercure-bundle ###
# See https://symfony.com/doc/current/mercure.html#configuration
- MERCURE_PUBLISH_URL=http://mercure/.well-known/mercure
+ MERCURE_PUBLISH_URL=http://localhost:3000/.well-known/mercure
# The default token is signed with the secret key: !ChangeMe!
MERCURE_JWT_TOKEN=...
###< symfony/mercure-bundle ###

Hub インストール

mercure は専用のプロトコルを使ってプッシュ通知を行います。専用のプロトコルでの GET / POST を処理する Hub と呼ばれるサーバーが必要なのでダウンロードします。私は Mac を使っているので _Darwin_x86_64 をダウンロードしました。

https://github.com/dunglas/mercure/releases/tag/v0.8.0

Hub サーバー起動

ダウンロードした圧縮ファイルを解凍して mercure のバイナリをターミナルから呼び出します。 プロダクション環境では jwt-keyorigin などもろもろ調整する必要がありそうですが、今回はローカルでの検証目的のためデフォルトをそのまま使う事にします。

$ cd {DOWNLOAD_DIR}

$ ./mercure --jwt-key='!ChangeMe!' \
  --addr=':3000' \
  --debug \
  --allow-anonymous \
  --cors-allowed-origins='*' \
  --publish-allowed-origins='http://localhost:3000'

macOS Catalina では初回のみ 開発元を検証できないため開けません というメッセージが表示されます。「システム環境設定 > セキュリティとプライバシー > ダウンロードしたアプリケーションの実行許可 > このまま許可」の操作を行ってください。

ブラウザで http://localhost:3000 を開くとこのような mercure のデバッグ用ページが表示されます。

デバッグ用ページで Pub/Sub を試す

mercure のデバッグ用ページではプッシュ通知(Publish)と待ち受け(Subscribe)を試す事ができます。

  • 「Subscribe」でトピックを入力し Subscribe ボタンを押します(青枠)
  • 「Publish」でトピックとデータを入力し Publish ボタンを押します(赤枠)
  • Publish ボタンを押すごとにプッシュ通知の内容が追加されます(オレンジ)

トピックはプッシュ通知のチャンネルのようなもので、チャンネル別にプッシュ通知や待ち受けを整理する事ができます。

Symfony コマンドの作成

公式ドキュメントでは コントローラを使用したプッシュ通知のサンプルコード が紹介されていますが、実際の運用ではバッチ処理の完了などがプッシュ通知の主なタイミングになると予想されます。そのためコンソールコマンドを経由したプッシュ通知を試す事にしました。

まずコンソールコマンドを作成します。

$ bin/console make:command push-message

作成したコマンドのソースコードを下記のように変更します。

<?php
namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Mercure\PublisherInterface;
use Symfony\Component\Mercure\Update;

class PushMessageCommand extends Command
{
    protected static $defaultName = 'app:push-message';

    private $publisher;

    public function __construct(PublisherInterface $publisher)
    {
        $this->publisher = $publisher;
        parent::__construct();
    }

    protected function configure()
    {
        $this
            ->setDescription('push message demo')
            ->addArgument('message', InputArgument::REQUIRED, 'Argument description')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $message = $input->getArgument('message');
        $this->push($message);
        $io->success(sprintf('%s pushed.', $message));

        return 0;
    }

    private function push($message)
    {
        $update = new Update('topic1', $message);
        call_user_func($this->publisher, $update);
    }
}

Symfony\Component\Mercure\PublisherInterface

<?php
public function __construct(PublisherInterface $publisher)
{
  ...

mercure コンポーネントを autowire で注入するとデバッグ画面で試した「Publish」と同じ動作を Symfony コードから実行する事ができます。公式のサンプルコードではクラスを注入していますが、mercure の新しいリリースによりインターフェースに代わっています。
https://github.com/symfony/mercure-bundle/releases/tag/v0.2.0

Symfony\Component\Mercure\Update

<?php
private function push($message)
{
    $update = new Update('topic1', $message);
    call_user_func($this->publisher, $update);

Update はペイロードを格納するクラスです。このクラスを Publisher に渡すと環境変数に設定された Hub サーバーにリクエストを送信するという仕組みになっているようです。

コマンドを経由してプッシュ通知を試す

実際にコマンドを叩いてプッシュ通知を送信してみましょう。

$ bin/console app:push-message "テスト!!!"

mercure のデバッグ用ページを見てみましょう。 新しいプッシュ通知を受信しているはずです。

percel で待ち受ける

mercure のデバッグ用ページを見ればプッシュ通知が正常に動作している事は一目瞭然ですが、せっかくなのでフロントエンドでの待ち受け処理も書いてみましょう。

最小最速で Web アプリケーションを作るには PARCEL がおすすめです。

$ mkdir my-app && cd $_
$ touch index.html
$ touch index.js
<!-- index.html -->
<html>
  <head>
    <script src="./index.js"></script>
  </head>
  <body>Hello my-app!</body>
</html>
$ npm install -g parcel-bundler
$ parcel ./index.html

ブラウザで http://localhost:1234 を開くと「Hello my-app!」と表示されます。 ビルドからサーバー起動までたったの1秒!速いですね。

待ち受け用のコード

// my-app/index.js

const es = new EventSource(
  'http://localhost:3000/.well-known/mercure?topic=topic1'
);

es.onmessage = e => {
    console.log(e.data);
}

JavaScript のコードはたったこれだけです。

ブラウザが EventSource に対応している必要がありますのでご注意ください。
MDN EventSource
https://developer.mozilla.org/ja/docs/Web/API/EventSource

Symfony のプッシュ通知を JavaScript でキャッチする

ここまでの手順で準備は全て完了しています。 Symfony のコマンドを叩いて JavaScript がキャッチする事をブラウザで確認してみてください。

終わりに

手順の紹介は以上です。 Symfony4+ であれば簡単に SSE が利用できるのでポーリングに代わる新しい方法として、ぜひプロダクション環境でも使ってみたいなと思います。

明日は kojirock5260 さんの記事です。 よろしくお願いします!