徒然なるままに プログラミングメモや日々の生活などつれづれとつづっていくblog

2017年10月1日

Cakephp3.x系でSAML認証を実装する

Filed under: apache,Cakephp3,OSS,PHP,SAML認証 — ranpei @ 7:12 AM

今回は完全プライベートなネタではないのですが、

Cakephp3を使ってSAML認証を行うプラグインを作成したのでその使い方をまとめようと思います。

(SAMLの設定が結構面倒なのでまとめておきたかったってのもあります。)

 

■環境構築

何はともあれ、まずは環境構築です。

今回の構成は以下のようになります。

・基盤

OS: CentOS7.2

Webサーバー:Apache2.4

PHP: 5.6

・アプリケーション

Identity Provider: SimpleSAMLphp

Service Provider: Cakephp3.x + 自前プラグイン(SamlAuthenticationPlugin)

 

●Apacheインストール

# yum install httpd

●PHP5.6インストール

普通にyumでインストールすると5.4系が入るため、

ここを参考にEPELとRemiリポジトリのパッケージを使用します。

# yum install epel-release
# rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
# yum install --enablerepo=remi,remi-php56 php php-devel php-mbstring php-mysql php-pdo php-gd php-xml php-mcrypt php-gmp php-intl

バージョンを確認してみましょう。

# php -v
PHP 5.6.31 (cli) (built: Jul 6 2017 08:06:11)
Copyright (c) 1997-2016 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies

 

Cakephpのパッケージ管理に使用されているためComposerをインストールします。


# curl -sS https://getcomposer.org/installer | php
# mv composer.phar /usr/local/bin/composer

 

●SimpleSAMLphp配置

SimpleSAMLphpを配置します。配置先はhttp://xxx.xxx.xxx/simplesaml/とします。

 

まずは、公式サイトからファイルを落としてきましょう。

DLし終わったらSCPなどを使ってWebサーバーにアップロードしてください。

 

●SimpleSAMLphpの設定

ファイルの解凍と配置を行います。(私は/var/www/simplesaml/に配置しました)

# tar zxvf simplesamlphp-1.14.16.tar.gz
# mv simplesamlphp-1.14.16 /var/www/simplesaml

 

●Cakephp3.x + 自前プラグイン

まずはCakephpを配置します。(私は/var/www/ssoapp/に配置しました)

# composer self-update && composer create-project --prefer-dist cakephp/app ssoapp

 

pluginsフォルダにSAML認証プラグインを配置します。

# cd ssoapp/plugins
# git clone https://github.com/gittrname/SamlAuthenticationPlugin.git

 

プラグインを有効化しましょう。

# cd ..
# vi config/bootstrap.php
// 最下部に追記
Plugin::load('SamlAuthenticationPlugin', ['bootstrap' => true, 'routes' => true]);
# vi composer.json
// 以下を編集
"josegonzalez/dotenv": "2.*"
↓
"josegonzalez/dotenv": "2.*",
"onelogin/php-saml": "^2.11.0"
 
"psr-4": {
"App\\": "src"
}
↓
"psr-4": {
"App\\": "src",
"SamlAuthenticationPlugin\\": "plugins/SamlAuthenticationPlugin/src"
}

 

終わったらcomposerで外部ライブラリのインストールと

autoloadの構築を行います。

# composer install

 

 

●ApacheのAlias設定

# vi /etc/httpd/conf.d/saml.conf ← 新規作成
// 以下を記載
Alias /simplesaml/ /var/www/simplesaml/www/
<Directory /var/www/simplesaml/www/>
AllowOverride None
Require all granted
</Directory>

Alias /ssoapp/ /var/www/ssoapp/webroot/
<Directory /var/www/ssoapp/webroot/>
AllowOverride None
Require all granted
<IfModule mod_rewrite.c>
RewriteEngine On
# RewriteRule ^(.*)/$ /ssoapp/$1 [L,R=300]
RewriteBase /ssoapp
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>
</Directory>

Apacheを再起動します。

# systemctl restart httpd

 

ここまででとりあえず前段階終了です。

・・・・そう!前段階なんです。この後の設定が回りくどく苦戦しました;;;

 

ざっくり行くと、まずIdpの証明書情報などをSPに登録、

次にSPの証明書情報などをIdpに登録する流れになります。

 

●Idpの証明書情報をSPに登録

登録する前にIdp側で証明書を作成します。

(実働環境だとオレオレではなく正規に発行してもらったものを使用するんでしょうね)

# cd /var/www/simplesaml/cert
# openssl req -newkey rsa:2048 -new -x509 -days 3652 -nodes -out server.crt -keyout server.pem

SAML2.0-Idpのモジュールを有効化します。

# vi /var/www/simplesaml/config/config.php
// 以下を変更
'enable.saml20-idp' => false,
↓
'enable.saml20-idp' => true,
 # touch modules/exampleauth/enable 

終わったらhttp://xxx.xxx.xxx/simplesaml/saml2/idp/metadata.phpにアクセスしてみましょう。

XMLが表示されたらその内容をメモっておきます。

必要となるのは「md:EntityDescriptorのentityId」「ds:X509Certificateの証明書文字列」

「md:SingleLogoutServiceのLocation」「md:SingleSignOnServiceのLocation」です。

 

では、SPに登録しましょう。

・・・・・とその前にSPの証明書を作成します;;;(またかよ・・・)

これも実働環境だと(ry

# cd /var/www/ssoapp/plugins
# mkdir cert
# cd cert
# openssl req -newkey rsa:2048 -new -x509 -days 3652 -nodes -out server.crt -keyout server.pem

 

作成した証明書をプラグインに設定します。

# cd ..
# cd config
# vi app.php
<?php
return [
'saml_config' => [
'baseurl' => 'http://xxx.xxx.xxx/ssoapp',
'sp' => [
'entityId' => 'http://xxx.xxx.xxx/ssoapp',
'assertionConsumerService' => [
'url' => 'http://xxx.xxx.xxx/ssoapp/saml-auth/login',
],
'singleLogoutService' => [
'url' => 'http://xxx.xxx.xxx/ssoapp/saml-auth/logout',
],
'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
'x509cert' => '{server.crtの中身をコピペ}',
'privateKey' => '{server.pemの中身をコピペ}'
],
'idp' => [
'entityId' => 'md:EntityDescriptorのentityId',
'singleSignOnService' => [
'url' => '{md:SingleSignOnServiceのLocation}',
],
'singleLogoutService' => [
'url' => '{md:SingleLogoutServiceのLocation}',
],
'x509cert' => '{ds:X509Certificateの証明書文字列}'],
]
];

 

保存したらhttp://xxx.xxx.xxx/ssoapp/saml-auth/metadataを表示してみてください。

XMLが表示されたらOKです。例によってこのXMLの情報をIdp設定で使用します。

 

●SPの証明書情報をIdpに登録

# vi /var/www/simplasaml/metadata/saml20-sp-remote.php
// 以下を追記
$metadata['http://xxx.xxx.xxx/ssoapp/'] = array(
'AssertionConsumerService' => 'http://xxx.xxx.xxx/ssoapp/saml-auth/login',
'SingleLogoutService' => 'http://xxx.xxx.xxx/ssoapp/saml-auth/logout',
'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
'simplesaml.nameidattribute' => 'uid',
'simplesaml.attributes' => FALSE,
);

 

認証用アカウント(ID=test, Pass=test)を作成

# vi /var/www/simplasaml/config/authsources.php
<?php
$config = array(
    'example-userpass' => array(
        'exampleauth:UserPass',
        'test:test' => array(
            'uid' => array('test'),
            'eduPersonAffiliation' => array('member', 'employee'),
        ),
    ),

これで完了です。

 

 

■動作確認

http://xxx.xxx.xxx/loginにアクセスするとIdp側のログイン画面にリダイレクトされます。

そこで認証が完了すればSP側に戻って認証情報が参照できるはずです。

 

・動作サンプル




 

 

 

参考)

 

2015年9月30日

PHPフレームワーク公開

Filed under: PHP,オープンソース,ソース配布 — ranpei @ 3:40 AM

仕事でPHPフレームワークを使ったときに以下の不満点がふつふつと湧き上がってきた。

・クラス名やメソッド名がURIパスになってしまう。

・各ファイルの配置先があらかじめ決められており自由度がない。

・内蔵ライブラリは便利だが外部ライブラリにもっと良いものがあるのに導入に手間がかかる。

 

上記の一部の不満点はメンバーから上がってきたもので

そのときはクラス名とメソッド名の変更で対応したもののもっとうまいやり方があったのでは?と思っていた。

 

もともとPHPは趣味でやっておりオレオレフレームワークで実装することが主だったため

既存フレームワークの良いところを取り入れつつ上記の不満点を解消できないかと試行錯誤していたものが形になったため

せっかくだから公開してみようとということになった。

 

ダウンロードから設置までの流れはこちらにまとめている。(内容が薄いため今後拡充していく予定)

 

現状ではフレームワークの骨組みはどのように作られているかという勉強用のソースという意味合いが強い。

最終的には小規模なシステム開発に向いたフレームワークとしていきたい。

(さすがに中大規模は無理なのでメジャーなPHPフレームワークを使用していただきたい。)

 

あと、ソース類はgithubなどのサービスを利用して配布していければ良いなーとか考えていたり・・・

2014年5月26日

あんてなサイトのサンプルソース公開

Filed under: PHP,アンテナサイト,ソース配布 — ranpei @ 3:53 AM

話題にしたの結構前ですが、公開するのすっかり忘れていました;;
というわけでソースを公開いたします。

基本的な部分は気ままアンテナ☆ミヾ(*´∇`)σと同じですが、
HTML部分は必要最低限のみを記載してシンプルにしています。

配布・解説等は試験的にConcrete5で構築したサイトで行っていますのでこちらをご覧ください。

ご質問等ありましたらコメント等でお願いします。

あんてなサイトサンプルダウンロード先:徒然なるままに

2014年5月23日

concrete5で日本語が入力できない・・・

Filed under: apache,OSS,PHP,オープンソース — ranpei @ 12:53 AM

現在ブログをCMSツールに移行する計画を実行中なのだが、
その過程で起こった問題を記録のためにメモって置く。

タイトルの通りConcrete5日本語版をインストールして、
さあ、記事を編集だ~~と思っていた矢先
日本語を入力しても化けて表示される問題にぶち当たった。

公式サイトの『よくあるインストール時の問題』に日本語が入力できない場合について書かれているのだが
「これは、お使いのサーバーの設定が、きちんと行われていない為に発生する問題だと思われます。”だと・・・
公式にも書いている.htaccessの記載もやっとるっちゅ~ねん。
MySQLの文字コードもUTF-8だし・・・

いろいろ試してみたところ.htaccessに以下の設定を追加することで解決しました。

php_flag mbstring.encoding_translation Off

2013年11月10日

Webサイトのキャプチャーを作る

Filed under: PHP,wkhtmltoimage,PC — ranpei @ 7:23 AM

現在私が管理しているWebサイトで、リンク先がサイト名だけではわかりにくいな~と思ったので、
マウスオーバーするとリンク先のイメージが表示されるような機能を付けることにしました。
その時のメモです。

今回の想定ではリアルタイムにスクリーンショットを作成する必要はないので(負荷が高くなるのでそもそもやりたくない)
wkhtmltoimageを利用して一定時間ごとにバッチでキャプチャーを撮る方式にしました。

wkhtmlimageのインストール

まず、wkhtmltoimageをインストールします。
インストールサーバーはUbuntu13.04(64bit)です。

1. ファイルをダウンロード

#wget http://wkhtmltopdf.googlecode.com/files/wkhtmltoimage-0.10.0_rc2-static-amd64.tar.bz2

最新のwkhtmltoimage-0.11.0_rc1-static-amd64.tar.bz2だとエラーが出てキャプチャーが取れないので
1つ前のバージョンをダウンロードします。

2. 解凍する

#tar jxf wkhtmltoimage-0.10.0_rc1-static-amd64.tar.bz2

解凍すると wkhtmltoimage-amd64 というファイルができます。
これがそのまま実行ファイルですので ./wkhtmltoimage-amd64 http://www.google.co.jp test.png とコマンドを実行すれば
キャプチャーが作成できるはずです。

もし、ライブラリが足りないなどのエラーが表示された場合は

#sudo apt-get install wkhtmltopdf

を実行して wkhtmltopdf をインストールしてみてください。
もともとwkhtmltopdfの機能だったものを分離しているのでwkhtmltopdfをインストールすれば
必要なライブラリ類も自動的にインストールされちゃうわけです。

3. /usr/bin/に配置

#mv wkhtmltoimage-0.10.0_rc1-static-amd64 /usr/bin/wkhtmltoimage

私はこれだけでパスの設定などは特に必要なかったです。

4. 日本語フォントのインストール
このままだと日本語が文字化けするので日本語フォントをインストールします。
今回はIPAフォントを利用しました。
ダウンロードしたものを解凍して .ttfファイルを/usr/share/fonts/配下に配置するだけです。

#sudo cp ipaexg00201/*.ttf /usr/share/fonts/.

以上が終わればインストール完了です。

#wkhtmltoimage http://www.google.co.jp test.png

とコマンドを実行すればキャプチャーが作成できているはずです。

2013年7月7日

RSSリーダーを作ってみる その2

Filed under: PHP,アンテナサイト,ソース配布 — ranpei @ 1:38 AM

前回の公開したrssLoader
実はRSS2.0だとitemタグを解析できないというバグがある

どこが悪くて読み込めないかというと以下の部分

        // 各記事を取得する
        foreach($rssDom->item as $item) {
 
            $itemBean = new itemBean();

RSSのフォーマット・仕様・構造 – RSS1.0、RSS2.0、Content-Type」を見ていただくと分かるが
RSS1.0ではitemタグはchannelタグと同列のDom階層に存在するが
RSS2.0ではitemタグはchannelタグ配下のDom階層にある

RSS2.0を読み込むには「$rssDom->channel->item」としなければならないのだ

修正したソースは以下のようになる

<?php
require_once __DIR__."/channelBean.php";
require_once __DIR__."/itemBean.php";
require_once __DIR__."/rssException.php";
require_once __DIR__."/lib/httpConnecter.php";
require_once __DIR__."/lib/xmlLoader.php";

class rssLoader {

	const VERSION_1 = 1;
	const VERSION_2 = 2;

	const VERSION_ALL = 0;

	public function __construct() {
	}

	public function __destruct() {
	}

	/**
	 * RSSを読み込みchannelBeanで取得
	 *
	 * @param int $version 対象RSSバージョン指定
     *                    全バージョンのRSS  : rssLoader::VERSION_ALL
     *                    バージョン1.0のRSS : rssLoader::VERSION_1
     *                    バージョン2.0のRSS : rssLoader::VERSION_2
	 *
	 */
	public function loadRss($path, $version = self::VERSION_ALL) {

		/**
		 * RSSを取得
		 */
		$http = new httpConnecter();
		$http->connect($path);
		$rssStr = $http->loadText();
		if($rssStr === "") {

			// 取得できなければエラー
			throw rssException::notRss("RSSファイルの取得ができませんでした。");
		}


		/**
		 * RSSを解析
		 */
		$xml = new xmlLoader();
		$rssDom = $xml->parsor($rssStr);

		// すべてのバージョン
		if($version == self::VERSION_ALL) {

			try {

				return $this->loadRss2($rssDom);

			} catch (Exception $e) {

				return $this->loadRss1($rssDom);
			}
		}

		// rss1.0
		else if(isset($rssDom->attributes()->version) == false && $version == self::VERSION_1) {

			return $this->loadRss1($rssDom);
		}
		// rss2.0
		else if($rssDom->attributes()->version == 2 && $version == self::VERSION_2) {

			return $this->loadRss2($rssDom);
		}
		else {

			throw rssException::notMatchVersion("このRSSは正しい型式ではありません。");
		}

		return $channelBean;
	}

	/**
	 * RSS1.0を解析
	 * @param unknown $rssDom
	 */
	private function loadRss1($rssDom) {

		//if($rssDom->attributes()->version != self::VERSION_1) {

		//	throw rssException::notMatchVersion("このRSSはVertion1.0ではありません。");
		//}


		$channelBean = new channelBean();

		$channelBean->encoding = "UTF-8";
		$channelBean->rssVersion = self::VERSION_1;
		$channelBean->rssUrl = $rssDom->channel->link;


		// RSSの本体情報を取得
		if(isset($rssDom->channel->title)) {
			$channelBean->title = $rssDom->channel->title;
		}
		if(isset($rssDom->channel->link)) {
			$channelBean->link = $rssDom->channel->link;
		}
		if(isset($rssDom->channel->description)) {
			$channelBean->description = $rssDom->channel->description;
		}
		// dcの要素を取得
		$dc = $rssDom->channel->children('http://purl.org/dc/elements/1.1/');
		if(isset($dc->date)) {
			$channelBean->date = $dc->date;
		}
		if(isset($dc->language)) {
			$channelBean->language = $dc->language;
		}
		if(isset($dc->creator)) {
			$channelBean->creator = $dc->creator;
		}


		// ver1.0各記事を取得する
		foreach($rssDom->item as $item) {

			$itemBean = new itemBean();

			if(isset($item->title)) {
				$itemBean->title = $item->title;
			}
			if(isset($item->link)) {
				$itemBean->link = $item->link;
			}
			if(isset($item->description)) {
				$itemBean->description = $item->description;
			}
			if(isset($item->category)) {
				$itemBean->category = $item->category;
			}
			if(isset($item->pubDate)) {
				$itemBean->date = $item->date;
			}
			// dcの要素を取得
			$dc = $item->children('http://purl.org/dc/elements/1.1/');
			if(isset($dc->date)) {
				$itemBean->date = $dc->date;
			}
			if(isset($dc->subject)) {
				$itemBean->category = $dc->subject;
			}
			if(isset($dc->creator)) {
				$itemBean->creator = $dc->creator;
			}

			array_push($channelBean->itemList, $itemBean);
		}

		return $channelBean;
	}

	/**
	 * RSS2.0を解析
	 * @param unknown $rssDom
	 */
	private function loadRss2($rssDom) {

		if($rssDom->attributes()->version != self::VERSION_2) {

			throw rssException::notMatchVersion("このRSSはVertion2.0ではありません。");
		}

		$channelBean = new channelBean();

		$channelBean->encoding = "UTF-8";
		$channelBean->rssVersion = self::VERSION_2;
		$channelBean->rssUrl = $rssDom->channel->link;


		// RSSの本体情報を取得
		if(isset($rssDom->channel->title)) {
			$channelBean->title = $rssDom->channel->title;
		}
		if(isset($rssDom->channel->link)) {
			$channelBean->link = $rssDom->channel->link;
		}
		if(isset($rssDom->channel->description)) {
			$channelBean->description = $rssDom->channel->description;
		}
		// dcの要素を取得
		$dc = $rssDom->channel->children('http://purl.org/dc/elements/1.1/');
		if(isset($dc->date)) {
			$channelBean->date = $dc->date;
		}
		if(isset($dc->language)) {
			$channelBean->language = $dc->language;
		}
		if(isset($dc->creator)) {
			$channelBean->creator = $dc->creator;
		}


		// ver2.0各記事を取得する
		foreach($rssDom->channel->item as $item) {

			$itemBean = new itemBean();

			if(isset($item->title)) {
				$itemBean->title = $item->title;
			}
			if(isset($item->link)) {
				$itemBean->link = $item->link;
			}
			if(isset($item->description)) {
				$itemBean->description = $item->description;
			}
			if(isset($item->category)) {
				$itemBean->category = $item->category;
			}
			if(isset($item->pubDate)) {
				$itemBean->date = $item->date;
			}
			// dcの要素を取得
			$dc = $item->children('http://purl.org/dc/elements/1.1/');
			if(isset($dc->date)) {
				$itemBean->date = $dc->date;
			}
			if(isset($dc->subject)) {
				$itemBean->category = $dc->subject;
			}
			if(isset($dc->creator)) {
				$itemBean->creator = $dc->creator;
			}

			array_push($channelBean->itemList, $itemBean);
		}

		return $channelBean;
	}
}

上記ソースはほかにもネームスペース「dc」の要素が読み込めていない問題などを修正している

ダウンロードはこちらから→[ダウンロードが見つかりません]

2013年6月30日

アンテナサイト公開

Filed under: PHP,アンテナサイト — ranpei @ 7:19 AM

アンテナサイト作成の模様はまだ途中ですが
アンテナサイト自体は完成しているため公開します。

気ままアンテナ☆ミヾ(*´∇`)σ←今回作成したアンテナサイト
kimamaスクリーンショット

ファイルをローカルにダウンロードする

Filed under: PHP,アンテナサイト — ranpei @ 7:09 AM

前回の構成のためにRSSをダウンロードしてくる処理を作成してみます。

フォルダ構成は以下のようにしてみました。
antenaSiteDir

rssDownload.phpが本体でsite.csvから対象URLを取得してrssDataフォルダーにファイルを保存します。

以下がソースです。

<?php

define("RSS_DIR", "../rssData/");	// RSSデータのダウンロード先
define("CSV_FILE", "site.csv");		// RSS配信サイトのURLリスト
define("CRLF", "\r\n");

require_once __DIR__.'/rssLoader/rssLoader.php';

/**
 * CSVファイルから対象のRSSURL一覧を取得
 */
echo "load site csv start\n";

// CSVファイルをロード
$siteList = file(CSV_FILE, FILE_IGNORE_NEW_LINES);
if($siteList === false) {

	echo "not found ".CSV_FILE."\n";
	return;
}

echo "load site csv end\n";

/**
 * 各RSSダウンロード
 */
echo "download rss start\n";
$http = new httpConnecter();
try {

	$count = 0;
	foreach($siteList as $url) {

		echo "download url:".$url."\n";

		// 接続
		$http->connect($url);

		// 保存先ファイルオープン
		$filePath = sprintf("%s/%03d.rss", RSS_DIR, $count);
		$file = fopen($filePath, "wb");

		// ストリームでデータをロードし、ストリームで書き込む
		$buffer = null;
		do {

			// webからデータを取得する
			$buffer = $http->loadBinnaryStream(4086);

			// ファイルにデータを書き込む
			fwrite($file, $buffer);

			echo ".";

		} while(!is_null($buffer));
		echo "\n";

		// ファイルをクローズ
		fclose($file);

		$count++;
	}
} catch(Exception $e) {

	echo $e->getMessage()."\n";
}
echo "download rss end\n";

httpConnecterはrssLoaderが利用しているものを流用しています。
ぶっちゃけこれ使わなくてもいいんですけどね・・

これをcronで定期的回せば定期的にRSSを取得できます。

ソースのダウンロードはこちらから:[ダウンロードが見つかりません]

2013年6月28日

アンテナサイトの構成案

Filed under: PHP,アンテナサイト — ranpei @ 2:02 AM

前回、RSSをロードして情報を取得するクラスを作成した。
後はこのクラスを使って複数のRSS配信サイトからRSSを読み込んで表示するだけなのだが 、

ここで1つ注意すべき点がある。

それはアンテナサイトにアクセスがあるたび毎回RSS配信サイトにアクセスするような構成にしないことだ。

以下の図を見てほしい
アンテナサイトへのアクセスが少ない場合 アンテナサイトへのアクセスが多い場合
この用にアクセス数が増えればその分RSS配信サイトにも同じだけのトラフィック増加が起きることになる。
そしてサイトの表示時間も遅くなることが容易に想像だろう。

まあ、こんな注意いまさらって人が多いだろうが・・

だいたいの人は以下のように、RSSを取得するバッチと表示用の2つのプログラムを作成することになるだろう

antenaSite1

もしくはRSSを取得後静的なHTMLを吐き出すようにするって人もいるでしょう

antenaSite2

どちらも取得は一定時間ごとに行うのでRSS配信サイト側に係る負荷はアンテナサイトへのアクセス数が増えても増加しません。

また表示時間も 静的HTML > 表示用プログラム の順で早くなります。

今回は取得用と表示用に2つのプログラムを作成する方法を使って作成しようと思います。

では、次回に

2013年6月23日

RSSリーダーを作ってみる

Filed under: PHP,アンテナサイト,ソース配布 — ranpei @ 7:19 AM

ブログやニュースなどの更新情報を配信するために用いられるRSS
これを利用して複数のサイトの情報を取得表示しているのがアンテナサイトです。
今回ふと思い立ってこのアンテナサイトを作ることにしたので数回に分けて作成までの道のりをつらつらとつづって行こうと思います。
まずはRSSを解析する仕組みが必要なのですがググって見つかるソースは拡張モジュールのcurlを利用しているものばかり
基本的に拡張モジュールは使いたくないので自力で作ってみることにした。

・rssLoader.php

<?php

require_once __DIR__."/channelBean.php";
require_once __DIR__."/itemBean.php";
require_once __DIR__."/rssException.php";
require_once __DIR__."/lib/httpConnecter.php";
require_once __DIR__."/lib/xmlLoader.php";

class rssLoader {

	const VERSION_1 = 1;
	const VERSION_2 = 2;

	const VERSION_ALL = 0;

	public function __construct() {
	}

	public function __destruct() {
	}

	/**
	 * RSSを読み込みchannelBeanで取得
	 *
	 * @param int $version 対象RSSバージョン指定
     *                    全バージョンのRSS  : rssLoader::VERSION_ALL
     *                    バージョン1.0のRSS : rssLoader::VERSION_1
     *                    バージョン2.0のRSS : rssLoader::VERSION_2
	 *
	 */
	public function loadRss($path, $version = self::VERSION_ALL) {

		/**
		 * RSSを取得
		 */
		$http = new httpConnecter();
		$http->connect($path);
		$rssStr = $http->loadText();
		if($rssStr === "") {

			// 取得できなければエラー
			throw rssException::notRss("RSSファイルの取得ができませんでした。");
		}

		/**
		 * RSSを解析
		 */
		$xml = new xmlLoader();
		$rssDom = $xml->parsor($rssStr);

		$channelBean = new channelBean();

		$channelBean->encoding = "UTF-8";

		// rss1.0
		if(isset($rssDom->attributes()->version) == false) {
			$channelBean->rssVersion = self::VERSION_1;
			$channelBean->rssUrl = $rssDom->channel->link;

			if($version != self::VERSION_1 && $version != self::VERSION_ALL) {

				throw rssException::notMatchVersion("このRSSはVertion1.0ではありません。");
			}
		}
		// rss2.0
		else if($rssDom->attributes()->version == 2) {

			$channelBean->rssVersion = self::VERSION_2;
			$channelBean->rssUrl = $rssDom->channel->link;

			if($version != self::VERSION_2 && $version != self::VERSION_ALL) {

				throw rssException::notMatchVersion("このRSSはVertion2.0ではありません。");
			}
		}

		// RSSの本体情報を取得
		if(isset($rssDom->channel->title)) {
			$channelBean->title = $rssDom->channel->title;
		}
		if(isset($rssDom->channel->link)) {
			$channelBean->link = $rssDom->channel->link;
		}
		if(isset($rssDom->channel->description)) {
			$channelBean->description = $rssDom->channel->description;
		}
		// dcの要素を取得
		$dc = $rssDom->channel->children('http://purl.org/dc/elements/1.1/');
		if(isset($dc->date)) {
			$channelBean->date = $dc->date;
		}
		if(isset($dc->language)) {
			$channelBean->language = $dc->language;
		}
		if(isset($dc->creator)) {
			$channelBean->creator = $dc->creator;
		}

		// 各記事を取得する
		foreach($rssDom->item as $item) {

			$itemBean = new itemBean();

			if(isset($item->title)) {
				$itemBean->title = $item->title;
			}
			if(isset($item->link)) {
				$itemBean->link = $item->link;
			}
			if(isset($item->description)) {
				$itemBean->description = $item->description;
			}
			if(isset($item->category)) {
				$itemBean->category = $item->category;
			}
			if(isset($item->pubDate)) {
				$itemBean->date = $item->date;
			}
			// dcの要素を取得
			$dc = $item->children('http://purl.org/dc/elements/1.1/');
			if(isset($dc->date)) {
				$itemBean->date = $dc->date;
			}
			if(isset($dc->subject)) {
				$itemBean->category = $dc->subject;
			}
			if(isset($dc->creator)) {
				$itemBean->creator = $dc->creator;
			}

			array_push($channelBean->itemList, $itemBean);
		}

		return $channelBean;
	}
}

rssLoaderで解析するとchannelBeanクラスが返却される。
このchannelBeanクラス内部にitemBeanのリストがありそこに個々の記事のデータがあるといった感じ
itemBeanがchannelBeanの内部にあるのはRSS2.0ではchannelタグの内部にitemタグがあるからといった理由から

使い方は以下
・rssTest.php

<?php

require_once "rssLoader.php";

$rssLoader = new rssLoader();
try {
	$channel = $rssLoader->loadRss("http://watch2ch.2chblog.jp/index.rdf");// ネットワーク上のファイルの場合
	//$channel = $rssLoader->loadRss("/var/www/html/rss.rdf");// ローカルファイルの場合
} catch (Exception $e) {

	echo $e->getMessage();
}

?>
<html lang="ja">
	<head>
		<meta charset="utf-8" />
		<title>RSS</title>
	</head>

	<body>
		<h2><?=$channel->title ?></h2>
		<table border="1">
			<tr>
				<th>タイトル一覧</th>
				<th>記事</th>
				<th>作成日</th>
			</tr>
			<?php foreach($channel->itemList as $item): ?>
			<tr>
				<td><?=$item->title ?></td>
				<td><?=$item->date ?></td>
			</tr>
			<?php endforeach; ?>
		</table>
	</body>
</html>

ソースコードのダウンロードはこちら:[ダウンロードが見つかりません]

これでRSSを読み込んで記事情報を取り出すことはできました。

次回に続きます。

追記
このrssLoaderにはRSS2.0が読み込めないバグがあります。
修正版は後日別の記事にて掲載いたします。

Powered by WordPress