ホーム > タグ > php

php

WordPressのguidはGUIDじゃない

だからパーマリンクの設計はまず最初にやれとあれほど言っただろ(挨拶).ということで,パーマリンク関連ではまったので,愚痴エントリ.解決策もあるよ.

発生した問題

get_posts関数で記事一覧を取得して,その返り値からpost_titleとguidを使ってリンクを作っていたんです.まぁ,問題なく上手くいってました.しかし,ある時(っていうかついさっきだけど)パーマリンク構成を変更しました.そうすると,途端に問題と出くわしました.

どんな状態かといえば,パーマリンクを変更したのに,guidが変わっていないのです.そのため,リンクがあっちこっちでぶっ壊れました.そこで,調べてみたところ,どうもこのguidはパーマリンクではないようなのです.今回のようなパーマリンクの変更だけではなく,ブログを引っ越した際などでドメインが変わった場合も同様の問題が発生するようです.つまり,このguidってのは,記事を指し示すURIにはなってなくて,URIのように見えるだけで,単なるidentifierに過ぎないようなのです.よくよく読んでみると,確かにそのように書いてあります.

It’s merely a unique identifier, which so happens to be a link to the post at present.

Function Reference/get post ≪ WordPress Codex

それ,guid (Global Unique Identifier)っていうか??なんでHTTPなURI表現なんだ??ということで,漫然とguidを使うと,はまります.

解決策

guidは当てにならないので,代替策が必要になります.使えそうなのは,明確にパーマリンクだといっているget_permalink関数です.引数に記事IDを渡すとパーマリンクが返ってくるそうです.ですので,以下のようなコードが有力でしょうか.

$posts = get_posts('numberposts=-1);
foreach($posts as $post) {
  $links[$post->post_title] = get_permalink($post->ID);
}

まぁ,こんなもんですよね.

エントリ名をトリガにしたオートリンクの方法

ブログを長いこと書いていると,エントリ数が膨大になるので,過去に書いた記事を探し出してリンクを張ることが面倒になってきます.エントリ名をトリガにして,テキストをハイパーリンクに置換して,オートリンクすることができれば,便利だなぁなんて思うわけです.ですので,それをWordpressで実現してみます.方法はかなり泥臭い手法です.

前提条件と基本戦略

前提条件を以下のように設定します.

  • エントリ本文に対してオートリンクを行う.
  • エントリ本文は基本的にプレーンテキストである.
  • 置換対象文字列は既存のエントリ名とする.
  • 置換後は既存エントリにハイパーリンクを張る.

上記条件を前提として,オートリンクを実現します.基本的な戦略は以下の通り.

  • 既存エントリ名を全て取得する.
  • エントリ本文から既存エントリ名があるかどうかを探す.
  • もし見つかれば,オートリンクに置換する.

既存エントリ名を全て取得する

これは別に難しくないですね.Wordpressの関数を使って,サクッと作ると,以下のような感じでしょうか.

$posts = get_posts('numberposts=-1');
foreach($posts as $post) {
  $links[$post->post_title] = $post->guid;
}

こんな感じで,$link[]な連想配列にkeyとしてエントリ名,valueとしてパーマリンクが格納されます.簡単ちんね.この辺りは,頻繁に変わらないだろうから,キャッシュを仕掛けるとか,どっかに定数として準備しておいても良いかも.

エントリ本文に既存エントリ名があるかどうかを探す

では,文字列置換をしましょう.PHPで文字列置換といえば,str_replaceですが,単純に使うとマズイです.以下のような例を想定しましょう.

私はコーヒーよりもコーヒー牛乳が好きです.

ここで,既存エントリとして「コーヒー」および「コーヒー牛乳」があったとします.置換順序が「コーヒー」「コーヒー牛乳」だった場合,以下のように置換されて行きます.ここでは,マッチした部分を強調で示します.

私はコーヒーよりもコーヒー牛乳が好きです.

次に「コーヒー牛乳」で置換したいのですが,既に「コーヒー」で置換されているので,マッチしません.マズイですね.ですので,置換文字列は,文字数が多い順に並んでいる必要性があると思います.そうすることで,最長一致による文字列置換が実現されます.では,それだけでよいでしょうか?置換順序が「コーヒー牛乳」「コーヒー」の順番になっても,次の問題が発生します.

私はコーヒーよりもコーヒー牛乳が好きです.

この文字列に対して,「コーヒー」の置換を行うと,「コーヒー牛乳」の「コーヒー」が再度置換されます.結果として,入れ子のaタグで挟まれる形になります.これはマズイです.つまり,単純な置換はできないので,正規表現を使うことにしましょう.

正規表現による戦略

mb_ereg_replaceを用います.置換文字列が対象文字列内にあり,さらにaタグで挟まれていない場合に,置換を行いましょう.皆さまご存じの通り,私は正規表現を苦手としており,四苦八苦です.ですので,一生懸命探しました.しかし,結構見つからないのです.出てくるのは,HTMLタグの除去とか,URLっぽい文字列を見つけたらオートリンクで置換とかばっかりです.ちょっと違うのです.しかし,需要がないはずはないと思い,頑張って探したところ,見つかりました.ググリ力は大事ですね!

とりあえず、「aタグの内部には他のタグは含まれていない」という前提の元で話を進めます。

$string = "朝一番のコーヒーは<a href='index.html'>3時のコーヒーや</a>食後のコーヒーより旨い";
$pattern = 'コーヒー(?![^<]*</a>)';
$result = mb_ereg_replace($pattern, '紅茶', $string);
echo $result;

タグにはさまれている文字以外を置換したい – PHP – 教えて!goo

ktkr!これでバッチリです.

functions.phpでフィルターフックをかける

では上記基本検討に従って,エントリ本文を表示する際に,エントリ本文中に既存エントリ名があった場合,オートリンクを張るような仕掛けを作ります.今回は,テーマで使われるfunctions.phpにフィルターフックを仕掛けてみました.対象とするフィルタフックはthe_contentです.つまり,まとめると,以下のようなコードをfunctions.phpに書きます.

function myautolink($content='') {
  mb_regex_encoding('UTF-8');
  $links = array();
  $length = array();

  $posts = get_posts('numberposts=-1');
  foreach($posts as $post) {
    $links[$post->post_title] = $post->guid;
    $length[$post->post_title] = mb_strlen($post->post_title);
  }
  arsort($length);

  foreach($length as $link => $val) {
    $pattern = $link.'(?![^<]*</a>)';
    $replacement = '<a href="'.$links[$link].'">'.$link.'</a>';
    $result = mb_ereg_replace($pattern, $replacement, $content);
    if(false != $result) {
      $content = $result;
    }
  }
  return $content;
}
add_filter('the_content', 'myautolink');

簡単ね!

まとめ

エントリ数が膨大だったり,置換対象の文章が長い場合には,性能が低下する可能性が高くなるので,キャッシュを準備するとか,なんかそういう工夫が必要になります.それについて,今回は触れないので,必要があれば工夫して下さい.

WordPressでエントリを50音順にソートする試み

WordPressのエントリを「あいうえお」順にソートしたかったのです.一般的に考えて,こんな方法で取り出します.

query_posts('showposts=-1&orderby=title&order=asc');

あると思います.しかし,これには日本語特有の問題があります.アルファベットやひらがなやカタカナはまぁまぁそこそこ思い通りに出力されると思いますが,漢字が入ると途端にダメです.当たり前ですね.「海」を「うみ」と読むのか「かい」と読むのかは字面からでは判断できません.そのため,日本語で書かれたエントリタイトルを単純な方法でソートしても,期待通りの結果を得ることはできません.

では,どうしますか?以下のような解決方法が考えられます.

  1. タイトルに漢字を使わない
  2. タイトル以外の要素を利用してソートする

タイトルに漢字を使わないというのは,なかなか強行的で,利便性への配慮が欠けすぎています.非現実的なソリューションです.非実在解決法.では,タイトル以外の要素を利用してソートすることを考えたとき,どのような手法があり得るのでしょうか.

  1. スラッグ
  2. カテゴリ
  3. タグ
  4. カスタムフィールド

まずスラッグです.スラッグは以下のように説明されています.

スラッグとは、投稿や固定ページを表すいくつかの単語のこと。スラッグは(WordPress によって自動生成された)URL に適する形式の投稿タイトルであることが多いが、その他の好きなフレーズでもかまわない。URL にあるコンテンツを説明する支援として、スラッグはパーマリンクで使われる。

用語集 – WordPress Codex 日本語版

好きなフレーズでも構わないので,ひらがなかカタカナでエントリの読みを振っておけば,ソートに利用できそうです.なかなかいいですね.

ではカテゴリはどうでしょうか.カテゴリでは問題は解決しません.エントリの数だけカテゴリを作るというなら,読みをカテゴリ名に与えて解決することもできますが,本来の利用方法から逸脱しすぎです.しかしながら,これはこれで別に役に立つ使い方があります.これは後述します.

タグを使う場合はどうでしょうか.タグに読みを与えれば解決できます.しかし,カテゴリと同じく,エントリの数だけタグが生成され,実に美しくないです.本来の用途でタグを使おうとしたときに,邪魔であるという問題もあります.ですので,積極的に採用したい解決策ではありません.

最後に,カスタムフィールドはどうでしょう.カスタムフィールドの使い方として,以下のような例示があります.

WordPress には、投稿者が投稿に「カスタムフィールド」を追加できる機能があります。この任意の情報は「メタデータ」と呼ばれており、たとえば以下のような情報を含めることができます。

  • 現在のムード: 幸せいっぱい
  • 今読んでいる本: 星の王子様
  • BGM: Rock Around the Clock
  • 今日の天気: 晴れ

さらに、ちょっとしたコードを付け加えるだけで、このメタデータに投稿の表示期限を付け加えたりすることも可能です。

カスタムフィールドの使い方 – WordPress Codex 日本語版

これは今回の目的にぴったりです.ではカスタムフィールドを使って実装しましょう.

カスタムフィールドを用いたエントリ50音ソート

まず前提として,以下を想定します.

  • カスタムフィールド名”yomi”に値として「ふりがな」を入力する
  • カスタムフィールド”yomi”を利用してエントリを50音ソートする
  • エントリタイトルは通常通りに英数漢字かなカナ混合
  • Exec-PHPなどが導入されていて,エントリやページにPHPを書ける状態である

では,以上のような想定環境下で全エントリを50音ソートさせてみます.以下のようなコードを「エントリ一覧」などとしたページに書くと,なんとなくそれっぽい出力が得られると思います.

query_posts('showposts=-1&orderby=meta_value&meta_key=yomi&order=asc');
while (have_posts()) : the_post();
echo '<p><a href="';
the_permalink();
echo '">';
the_title();
echo '</a></p>';
endwhile;

簡単ですね.

カスタムフィールドの入力を必須項目にしたい

ここからはおまけです.カスタムフィールドを使ってソートを実現しましたが,カスタムフィールド”yomi”に値が入力されていなければ,正しくソートされません.となれば,この項目は入力必須に指定したくなります.それを可能にするプラグインがCustom Field GUI Utility 3です.conf.iniを以下のように設定すれば,カスタムフィールド”yomi”は入力必須項目になります.

[yomi]
fieldname = ひらがなでの読み
type = textfield
class = post
default = あいうえお
size = 35
must = 1

もっといえば,ちゃんと「ふりがな」が入力されているかのバリデーションもやりたいところですね.そのときは,functions.phpでedit_postにフックを引っかけて,バリデーションしたら良いと思います.やってないので,コードは紹介できませんが.

201109181121追記

後述しますって書いておいて,カテゴリの使用方法を後述してませんでしたので,追記します.

50音順ソートはカスタムフィールドでやるとして,「あ行」とか「か行」とか,もっと細かく「あ」とか「い」とかやるには,カテゴリを打つのが便利です.カテゴリ毎のエントリをカスタムフィールドで並べれば良いだけなので.カスタムフィールド単体でやると,出力時に「あ」なのか「い」なのかを判別する処理を加える必要があります.それでも良いと思うけど.

参考

XML-RPCを使ってWordPressに記事を投稿する

何番煎じだかわかりませんが,備忘録として.XML-RPCを使って,スクリプトからWordpressに記事を投稿しまくる方法です.参考にしたのは以下のエントリ.

見ればわかりますが,プログラミング言語はPHPです.まずは,何はなくとも,PHPでXML-RPCを扱えないといけないので,PEARからインストールします.

# pear install XML_RPC

続いて,これもサンプルコードそのままなので,深く考えずに,書いていきます.ところが,どっこい!このエントリではカスタムフィールドの扱い方が説明されていません.なんてこったい!これは困りました.今回はカスタムフィールドに値を入れられないと,ものごっつ面倒くさいことになるような件数を突っ込むのです.ですので,どうしてもXML-RPCでやりたいので,調べたところ,以下のエントリがヒットしました.

完璧です.以上より,作成したコードは以下の通り.

require_once("XML/RPC.php");

$host = 'example.com';
$xmlrpc_path = '/wordpress/xmlrpc.php';
$appkey = '';
$user = 'username';
$passwd = 'password';

$title = 'エントリのタイトル';
$categories = array(
  new XML_RPC_Value('未分類', 'string'), );
$description = 'ここに本文を入れます.';
$custom_fields = array();
$custom_fields[] = new XML_RPC_Value(
  array(
    'key' => new XML_RPC_Value('yomi', 'string'),
    'value' => new XML_RPC_Value('ふりがな', 'string')
  ), 'struct');

$c = new XML_RPC_client($xmlrpc_path, $host, 80);
$appkey = new XML_RPC_Value($appkey, 'string');
$username = new XML_RPC_Value($user, 'string');
$passwd = new XML_RPC_Value($passwd, 'string');
$message = new XML_RPC_Message(
  'blogger.getUsersBlogs',
  array($appkey, $username, $passwd));
$result = $c->send($message);

if(!$result){
  exit('Could not connect to the server.');
} else if($result->faultCode()){
  exit($result->faultString());
}

$blogs = XML_RPC_decode($result->value());
$blog_id = new XML_RPC_Value($blogs[0]["blogid"], 'string');
$content = new XML_RPC_Value(
  array(
    'title' => new XML_RPC_Value($title, 'string'),
    'categories' => new XML_RPC_Value($categories, 'array'),
    'description' => new XML_RPC_Value($description, 'string'),
    'dateCreated' => new XML_RPC_Value(time(), 'dateTime.iso8601'),
    'custom_fields' => new XML_RPC_Value($custom_fields, 'struct'),
  ), 'struct');
$publish = new XML_RPC_Value(1, "boolean");
$message = new XML_RPC_Message(
  'metaWeblog.newPost',
  array($blog_id, $username, $passwd, $content, $publish));
$result = $c->send($message);

if(!$result){
  exit('Could not connect to the server.');
} else if( $result->faultCode() ){
  exit($result->faultString());
}

簡単ね!あとは自動でガンガン投稿するようにゴニョゴニョすれば万事快調.

イサム開発で使ったプログラミング等のTips

イサムの開発にあたって,いくつかの新しいテクニックを用いたので,新旧あわせて使ったテクニックを紹介したいと思います.

mod_rewriteでGETパラメータを変換

例えば,以下のようなURLを考えます.

http://4403.biz/ISAM/organization/NEC/

なんとなくパーマリンクっぽくて素敵ですよね.でも,この実体は以下のように処理したいと考えます.

http://4403.biz/ISAM/hoge.php?mode=organization&query=NEC

この変換をmod_rewriteで実現します.まぁ,Wordpressとかで使われている常套句ですね.mod_rewriteが有効な状態で,.htaccessに以下のように書きます.

RewriteEngine on
RewriteBase /ISAM/
RewriteRule "^(.*)/(.*)/$" "hoge.php?mode=$1&query=$2" [L]

簡単ね!ご存じだとは思いますが,これにGETパラメータがさらにくっつくような状況下では,QSAフラグも付けましょう.

hoge.phpでは受け取ったGETパラメータによって処理を振り分けたらいいと思います.これで実体を隠蔽しつつ,パーマリンクっぽいURLも提供できて,なんだかすごくいい感じです.クールURI!

mod_rewriteのBフラグ

上記の設定の後に,以下のようなアクセスを想定しましょう.

http://4403.biz/ISAM/organization/KDDI R&D Labs./

なんとなく上手くいかなさそうな気配がぷんぷんしますよね?実際にやってみると,このように展開されます.

http://4403.biz/ISAM/hoge.php?mode=organization&query=KDDI R&D_Labs_=

なんだか変なことになっている.紐解きましょう.GETパラメータがどうなっているかというと以下の通りです.

mode=organization
query=KDDI R
D_Labs_=

おい!って話ですね.問題点はR&Dの&をGETパラメータの接続詞と解釈してしまっています.解釈してしまっていますというか,それで正しいわけですが・・・.単純な解決策はURLエンコードです.しかし,そのためには,以下のようなアクセスを想定することになります.

http://4403.biz/ISAM/organization/KDDI%20R%26D%20Labs%2e/

こんなの恥ずかしくて見せられない!ということで,これをmod_rewrite中に実現したいわけですね.で,調べてみるとあるんですよ,これが.apache2.2.6以降らしいですが,Bフラグというものがあります.以下,引用.

B‘ (escape backreferences)
Apache has to unescape URLs before mapping them, so backreferences will be unescaped at the time they are applied. Using the B flag, non-alphanumeric characters in backreferences will be escaped. For example, consider the rule:
RewriteRule ^(.*)$ index.php?show=$1
This will map /C++ to index.php?show=/C++. But it will also map /C%2b%2b to index.php?show=/C++, because the %2b has been unescaped. With the B flag, it will instead map to index.php?show=/C%2b%2b.
This escaping is particularly necessary in a proxy situation, when the backend may break if presented with an unescaped URL.

mod_rewrite – Apache HTTP Server

簡単ね!というわけで,先ほどのRewriteRuleにBフラグを追加すると,こんなアクセスが来ても,

http://4403.biz/ISAM/organization/KDDI R&D Labs./

こんな風に渡されるので,

http://4403.biz/ISAM/hoge.php?mode=organization&query=KDDI%20R%26D%20Labs%2e

hoge.php側では$_GET[‘query’]をurldecodeしておけばいいですね.いい時代だね~.

memcachedの導入

インクリメンタルサーチでゴリゴリ検索させています.基本的にかなりの力業実装です.ですので,負荷を減らすためにキャッシュは重要になります.当初はMySQLのキャッシュ機能とAPCに丸投げしていたのですが,DBの更新が無ければ結果は不変なので,まるっとキャッシュしてしまえばいいのではないかと思ったので,memcachedしました.初挑戦です.

そもそもmemcachedとは何かといえば,以下のようなものだそうです.

Free & open source, high-performance, distributed memory object caching system, generic in nature, but intended for use in speeding up dynamic web applications by alleviating database load.

memcached – a distributed memory object caching system

ということで目的にピッタリです.で.基本的な使い方を確認しましょう.PHPではpecl::memcacheを使うのがいいのかな?似たものにpecl::memcachedもあります.よくわかりません.今回はmemcacheを使います.基本的な使い方は以下の通りです.

$mem = new Memcache;
$mem->addServer('localhost', 11211);
$val = $mem->get($key);
if(!$val) {
  //$valを作る作業
  $mem->add($key, $val, MEMCACHE_COMPRESSED, 3600);
}
$mem->close();

簡単ですね.これを応用して,イサムをキャッシュしまくります.方針としては,$keyをSQLクエリにして,$valをHTML出力文字列としています.つまりは,SQLクエリをキーにしてページフルキャッシュとほぼ同等です.これでキャッシュにヒットすればサックサクのはずです.

これをよくよく考えると,実行時間命なシステムでDBへの書き込みや更新がボトルネックになっている場合,とりまmemcachedにはき出しておいて,別プロセスで非同期にmemcachedから取り出してDBに格納するとかしてレイテンシを隠蔽できそう.そんなクリティカルなシステムを作ったことないですけどね.

エスケープとか

エスケープも別に珍しいことはしてなくて,strip_tagsやらmysql_escape_stringやらhtmlentitiesやらを使ってます.「エスケープは出力時に」という基本に忠実です.SQL文の構築はプレースホルダーを用いているので,そんなに変なことは起きないはずだと信じてます.攻撃しないで!

PHPの小ネタ

PHPはHTMLの中に直接スクリプトが書けるという実に変態な言語なので,様々な変態テクニックが駆使可能です.例えば,if文の途中でphp終了タグでぶった切って,HTMLを出力(というかそのまま表示)して,php開始タグで再びif文に復帰するとかいう常識的に考えられないような変態スーパーテクニックが利用可能です.可読性?なにそれ?誰が読むの?

それはそれとして,多少便利なものとしては,includeを用いたページ分割です.include命令によって別のphpファイルを読み込む(C言語の#includeのように)ことができるので,共通であるヘッダ部とフッタ部を別ファイルに分割することで,コード修正時の修正範囲を限定的にすることができます.ちなみに,これはHTMLヘッダ部でもできますので,かなり応用が利きますよ.

スーパーテクではありませんが,jQueryと組み合わせてajaxでインクリメンタルサーチやサジェストを実現しています.

1 / 212

Home > タグ > php

アフィリエイト

Return to page top