Tags: PHP

技術日記Laravel4PHP
LaravelをWindows環境で使うEdit

最近メインマシンをMacbookに移行したんだけど、Laravel4で作ったアプリケーションをWindows環境で動かさなければならない要件があったんで、Windows環境にセットアップして問題ないかどうか試してみた。手元にあるのがほぼまっさらなWindows8.1(on Parallels)だったので、それをベースに。

まずXAMPP for Windows をインストール。今だとXAMPP 1.8.3(Apache 2.4.7/MySQL 5.6.14/PHP 5.5.6)だった。

インストール後、XAMPPコントロールパネルからApacheを起動して、http://localhost/にアクセスできるかどうか確認。

続いてLaravelをcomposerを使ってインストールするために、まずはPHPにパスを通す。コントロールパネルのシステムの詳細設定-環境変数から、システム環境変数のPathにPHPのパス(C:\xampp\php)を追加する。

試しにコマンドプロンプトを起動して、php -iとか実行してちゃんとPHPにパスが通っているかどうかを確認。すでにコマンドプロンプトが起動済みだった場合、環境変数の変更を反映させるためにコマンドプロンプトを再起動。

続いて、composerをインストール。コマンドプロンプトで、

cd C:\xampp\php
php -r "eval('?>'.file_get_contents('https://getcomposer.org/installer'));"

を実行してcomposer.pharをダウンロード。これだけだと使い勝手が悪いので、C:\xampp\php内にcomposer.batファイルを作成。内容は、

@echo off if "%PHPBIN%" == "" set PHPBIN=C:\xampp\php\php.exe "%PHPBIN%" "C:\xampp\php\composer.phar" %*

にしておく。これでコマンドプロンプトから、

composer update

みたいな感じでcomposerを実行できる。

でcomposer create-project laravel/laravelしようと思ったら、途中でgitがなくてこけた。gitも必要だったっけ。

Git for Windows(msysgit)をインストール。コマンドプロンプトから使いたいんで、インストールオプションで実行スタイルを選択するところで、「Run Git from the Windows Command Prompt」とか、gitにPathを通してくれるオプションを選択する。

もう一回コマンドプロンプトを再起動。Laravelアプリケーションプロジェクトを作りたいディレクトリを適当に作成する。

mkdir c:\laraveltest
cd c:\laraveltest

あとは、

composer create-project laravel/laravel .

今度はエラーも出ずにプロジェクトの作成に成功。Apacheからこのプロジェクトディレクトリを参照したい場合は、C:\xampp\apache\conf\httpd.confから、

DocumentRoot "C:/xampp/htdocs"
<Directory "C:/xampp/htdocs">

となっている部分を、

DocumentRoot "C:/laraveltest/public"
<Directory "C:/laraveltest/public">

なんて変えてから、Apacheを再起動すると、http://localhost/ で今作ったプロジェクトにアクセスできる。まあC:\xampp\apache\conf\extra\httpd-vhosts.confとC:\Windows\System32\drivers\etc\hostsを使って、バーチャルホストにしておいた方が便利だろうけど。

前にWindows環境でLaravelを動かしたときは、mcryptとかのPHPモジュールを有効にしたり、timezone設定を追加したりとかしないといけなかったような気がするんだけど、今はそういう問題はないんだな。

このくらい手軽に動くなら、ピュアPHPで作れるようなアプリならば、Windows環境でも作りやすそうだ。

Published At2013-12-13 18:16Updated At2019-12-30 15:03

技術日記Laravel4PHP
Laravel 4.0.10からLaravel 4.1.5にアップグレードEdit

PHPフレームワークLaravelの4.1がリリースされた。手元に作りかけのLaravel 4アプリケーションがあったんで、今後のことを考えてとっととLaravel 4.1にアップグレードすることにした。手元のLaravel 4フレームワークのバージョンは4.0.10。

アプリケーションのスペックとしては、

  • 使用DBはsqliteのみ。ちょっとしたデートストア程度にしか使っていない。
  • JeffreyWay / Laravel-4-Generatorsで生成したマスタメンテ用Scaffoldページがいくつか。
  • JavaScriptアプリケーションのコンテナWebページが5ページほど。サーバーロジックはほとんどないのでroutes.phpでView::make()しているだけ。
  • メインのJavaScriptアプリケーションから呼ばれるJSON APIが10個ほど。DBとのやりとりがほとんどなので、routes.php内で直接Eloquent ORMを使って処理をして、JSONで結果を返すだけ。
といった感じ。小規模だしLaravel 4の機能は最低限しか使っていないから、移行のハードルは低い。

まずは、4.0から4.1へのアップグレードガイド日本語訳にざっと目を通す。私はLeanpubで日本語ドキュメントを買ってるんで、PDFで読んでるけど。

変更点としては、いくつかのファイルの差し替え、設定ファイル関連の変更がいくつか、コントローラの変更がちょっと。パスワードリマインダーも変更されたらしいけど、使ってないから内容は未チェック。作業ステップは一桁程度か。

まず、composer.jsonのrequireセクションにあるlaravel/frameworkのバージョンを4.0.から4.1.に変更。アップグレードガイドの手順とは違うけど、まずはそのままcomposer updateしてみる。

大量のremove&installの後で、Fatal Errorで終了。エラーは、

Error Output: PHP Fatal error: Call to undefined method Illuminate\Foundat
 ion\Application::redirectIfTrailingSlash() in /Users/ishinao/NetBeansProjec
 ts/webgldance/laravel/bootstrap/start.php on line 16

なんで、アップグレードガイドにある「Trailing Slashリダイレクトの削除」に引っかかったらしい。bootstrap/start.phpから,

$app->redirectIfTrailingSlash();

を削除。もう一度composer updateしたら、今度はエラーなしで正常終了した。

この段階で、試しにWebアプリケーションルートをブラウザで開いてみたところ、200 OKは返るけどコンテンツは何も返らない状態だった。

ということで、アップグレードガイドに書いてある作業を一つずつつぶしていくことにする。

まずはWebアプリケーション起動ファイルを新バージョンの内容に置き換え。public/index.phpを https://raw.github.com/laravel/laravel/master/public/index.phpからダウンロードして差し替え。

同様に、artisanコマンドの内容もhttps://raw.github.com/laravel/laravel/master/artisanからダウンロードした新バージョンの内容に置き換え。

続いて、設定ファイルを新バージョンの内容に追随させていく。

app/config/app.phpの内容をhttps://raw.github.com/laravel/laravel/master/app/config/app.phpの内容を元に書き換える。変わっているのはprovidersとaliasesのところなので、自分で追加/編集した内容がなければ、丸ごと新しい方で置き換える。自分で追加した内容があればそれを残しつつ。

新しく追加されたhttps://raw.github.com/laravel/laravel/master/app/config/remote.phpをapp/config/remote.phpとして保存。

セッション設定ファイル app/config/session.phpに

'expire_on_close' => false,

を追加。あとで、app/storage/logsを見てみたら、

'Undefined index: expire_on_close' in vendor/laravel/framework/src/Illuminate/Session/Middleware.php:171

が残っていたんで、これが起動時に画面が表示されない原因だったっぽい。

新機能の失敗したジョブキューからの復帰用設定を追加。app/config/queue.phpに

  'failed' => array(
    'database' => 'mysql', 'table' => 'failed_jobs',
  ),

を追加。

これでWebアプリケーションルートにブラウザからアクセスしてみたところ、無事画面が表示された。

他にもコントローラ関連の修正項目が書かれていたけれども、Generatorsで自動生成したコントローラクラスは、特に修正する必要がなかった。

アップグレードしたバージョンは4.1.0かと思っていたら、php artisanしてみたら、

Laravel Framework version 4.1.5

になっていた。

追記@2013/12/14

アップグレードガイドがアップデートされ、アップグレード手順で一つ重要な内容が欠けていたので、その部分を追記。

$app->redirectIfTrailingSlash() 相当の処理を.htaccessで行うようになったため、bootstrap/start.phpからそのコードを削除したにもかかわらず、上記手順では.htaccessをLaravel 4のままで使っている。public/.htaccessをhttps://raw.github.com/laravel/laravel/master/public/.htaccessに差し替える必要がある。

ちなみに修正内容として、以下の内容が追加されている。

# Redirect Trailing Slashes...
RewriteRule ^(.*)/$ /$1 [L,R=301]

また、Laravel Framework本体もアップデートされており、composer updateすると、

Laravel Framework version 4.1.8

になっていた。アップデートペース、はやっ。

Published At2013-12-13 15:19Updated At2020-01-01 15:39

技術日記Laravel4PHP
Laravel 4のエラーメッセージとフォームエラーEdit

Laravel 4では、

Redirect::to()->withErrors()

をすると、自動的にリダイレクト後のビュー内で$errorsに展開されるなど、エラーメッセージは特別な扱いになっている。このエラーメッセージは共通レイアウトなんかで、

@if ($errors->count() > 0)
<ul class="error">
@foreach ($errors as $error)
<li>{{{ $error }}}</li>
@endforeach
@endif

なんて感じに表示するのが普通だろうけど、フォームエラーなんかで、入力欄のそばにエラーメッセージを表示したい場合は、どういうふうに使い分けるといいんだろう。

(エラー表示コードを含む)共通レイアウトは使わず、フォーム表示ビューの方で、

{{ Form::open() }}
{{ Form::label('name') }}
{{ Form::text('name') }}
{{ $errors->first('name', '<p class="error">:message</p>') }}
{{ Form::close() }}

みたいな感じに書くのがいいのか。でもそうなると、(エラーメッセージ表示をしたい)フォームを含むかどうかで、レイアウトファイルを(エラー表示コードを含む物と含まない物で)切り替えることになるのが、あんまり気持ちよくない。

あるいは

[source language="php"] Redirect::to()->with('formErrors', $validator->messages) [/source]

なんて感じで、標準エラーメッセージを使わずに、独自のフラッシュセッションに保存して、

[source language="php"] if (Session::has('formErrors')) { View::share('formErrors', Session::get('formErrors'); } [/source]

とかやる方がいいんだろうか。これはこれでありだろうけど、せっかく標準エラーメッセージ機能があるのに、別でハンドリングするのも今ひとつのような気もする。

標準エラーメッセージを使いつつ、フォームビューの方でエラーメッセージを表示した場合は、何らかの方法でフラグをセットして、レイアウトの方のエラーメッセージを抑止するという方法も考えたんだけど、(コンテンツ)ビューから(レイアウト)ビューにデータを渡す、ほどよい方法が見当たらないんで、ペンディング中。

$errorsの実装クラスであるMessageBagにMessageBag::clear()とかが実装されていれば話が早かったんだけど、MessageBagは値を追加したりマージしたりはできても、削除とかリセットとかはできない模様。

今のところ、独自フラッシュセッションを使う方法が一番ましかなーとは思っているんだけど、もっときれいな方法があるという方、情報をお待ちしております。

追記@2013/07/18)

って話は、Laravel 4のwithErrors()は、レイアウトファイルなどを使って、サイト全体で共通表示するフラッシュメッセージ的なものと捉えた上で書いていたんだけど、なんか違うような気がしてきた。

withErrors()で渡すエラーは、共通フラッシュメッセージ的なものではなく、あくまでも特定機能(ページ)でのエラーを手軽にハンドリングするための入れ物なのか? 共通フラッシュメッセージは、with()とかを使った独自処理として書くべき?

たとえば、Laravel-4-Bootstrap-Starter-Site/app/views/notifications.blade.php at master · andrew13/Laravel-4-Bootstrap-Starter-Siteなんかを見ると、共通フラッシュメッセージ的な物としては、success、warning、error、infoを直接Session::flash()(=with())経由でやりとりして表示し、withErrors()(=$errors)はフォームエラー表示専用として扱っているようだ。

withErrors()はあくまでフォームエラー専用の入れ物であって、フラッシュメッセージとは別物、というのが妥当なアプローチなのかな。なんかフォームエラー専用の代物を、フレームワーク標準で自動的にビュー変数にまで代入しちゃうのは、やり過ぎ感が否めないんだけどなー。

Published At2013-07-17 19:07Updated At2020-01-01 15:40

技術日記Laravel4PHP
Laravel 4でfacebookログイン機能も作ってみるEdit

Twitterログイン機能を作ってみたついでに、facebookログイン機能も作ってみる。ただfacebookはあんまり好きじゃないんで、APIのドキュメントを眺めてみたことがある程度しか知識がないから、Laravel 4以前に手探りで実装することになるんだけど。

まず、facebookのPHP SDKをインストールする。composer.jsonのrequireに

    "facebook/php-sdk": "dev-master"

を追加して、composer updateするとfacebook PHP SDKがインストールされる。

続いて、facebookアプリケーションの登録。facebook developersで新しいアプリを作成し、アプリのIDとシークレットキーを取得しておく。その設定は、app/config/facebook.phpとして、

<?php

return array(
    'appId' => '[アプリID]',
    'secret' => '[シークレットキー]',
);

って感じで保存しておくと、Laravel 4アプリ内ではConfig::get('facebook')で取得できる。このあたりの設定ファイルの扱いもLaravel 4はシンプルでいいね。環境(本番/開発など)ごとにオーバーライドさせたければ、環境名のディレクトリを掘ってその中に入れればいいだけだし。

続いてDBの準備。Twitterログインと同じように、usersテーブルに直接facebookのユーザー情報を保持する設計にする。

        Schema::create('users', function($table) {
            $table->biginteger('id')->unsigned();
            $table->primary('id');
            $table->string('name');
            $table->string('access_token');
            $table->timestamps();
        });

facebookのユーザーIDは桁数が多いっぽいんでbigintegerにしておいた。あとは名前とアクセストークンを保存するようにしておく。

ログインルーティングは以下のような感じ。

Route::get('login', function() {
    $facebook = new Facebook(Config::get('facebook'));
    $config = array(
        'redirect_uri' => url('/login/callback'),
    );
    return Redirect::to($facebook->getLoginUrl($config));
});

facebook側の認証ページにリダイレクトさせ、その結果をコールバックURLに返してもらう。続いてコールバックのルーティング。

Route::get('login/callback', function() {
    $code = Input::get('code');
    if (strlen($code) == 0) {
        return Redirect::to('/')->with('message', 'ログインできませんでした。');
    }

    $facebook = new Facebook(Config::get('facebook'));
    $user_id = $facebook->getUser();

    if ($user_id == 0) {
        return Redirect::to('/')->with('message', 'ログインできませんでした。');
    }

    $user = User::find($user_id);
    if (empty($user)) {
        $user = new User;
        $user->id = $user_id;
    }

    $me = $facebook->api('/me');
    $user->name = $me['name'];
    $user->access_token = $facebook->getAccessToken();
    $user->save();

    Auth::login($user);

    return Redirect::to('/')->with('message', 'ログインしました。');
});

facebookログインの仕組みの全貌が把握できていないので、本当にこれでいいのかいまいち確信が持てていないが、少なくとも認証自体はできているはず。

Twitterの場合と同様に、最終的にはAuth::login()でLaravel側での認証情報をセットしている。

ログアウトは、Laravel側のログアウト処理だけ。

Route::get('logout', function() {
    Auth::logout();
    return Redirect::to('/');
});

ホーム画面にfacebookに登録されている名前くらいは表示するようにしておく。

Route::get('/', function()
{
    $data = array();

    if (Auth::check()) {
        $facebook = new Facebook(Config::get('facebook'));
        $me = $facebook->api('/me');
        $data['me'] = $me;
    }

    return View::make('home', $data);
});
// app/views/home.blade.php
@if (!empty($me))
    Hello, {{{ $me['name'] }}}
    <a href="/logout">ログアウト</a>
@else
    <a href="/login">ログイン</a>
@endif

今回はLaravel 4側の問題ではなくfacebook APIの仕組みを調べるのに手間取ってしまった。ちゃんと使うんだったら、もうちょっとfacebook APIのドキュメントを読み込まないとだめだなー。

Published At2013-07-12 15:45Updated At2020-01-01 15:42

技術日記Laravel4PHP
Laravel 4でTwitterログイン機能を作ってみるEdit

Laravel 4で、標準のAuth関連機能とTwitterのOAuth認証パッケージとを連動させて、Twitterでログイン&ユーザー登録機能を作ってみる。

Twitterのユーザー情報をそのまま自前のユーザーテーブルに保存して利用するんで、

php artisan migrate:make create_users_table

をしてmigrationファイルを生成し、以下のようなテーブルスキーマを定義する。

    public function up()
    {
        Schema::create('users', function($table) {
            $table->integer('id')->unsigned();
            $table->primary('id');
            $table->string('screen_name');
            $table->string('oauth_token');
            $table->string('oauth_token_secret');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::drop('users');
    }

プライマリーキーのusers.idに、Twitterのuser_idを直接保存して利用するので、$table->increments('id')にはせず(auto incrementにはせず)、unsigned intなプライマリーキーとして定義する。あと、Twitterの認証情報であるoauth_tokenとoauth_token_secretを保存するカラムも用意しておく。

Userモデルは以下のような感じ。

use Illuminate\Auth\UserInterface;

class User extends Eloquent implements UserInterface {

    public function getAuthIdentifier()
    {
        return $this->getKey();
    }

    public function getAuthPassword()
    {
        return null;
    }

}

Authで認証に利用するユーザークラスにはimplements UserInterfaceが必要なので、getAuthIdentifier()とgetAuthPassword()の二つを追加しておく。Twitter認証ではシステム独自のパスワードは必要ないのでgetAuthPassword()ではNULLを返している。

続いて、Twitter認証パッケージのTwitterOAuth Service Provider for Laravel 4をインストールする。インストール手順はドキュメント通り。composer.jsonに依存設定を追加して、composer updateしたり、config:publishしたり。

あと、dev.twitter.comでアプリケーション登録して認証キーの発行をおこない、app/config/packages/philo/twitter/config.phpにその値を登録しておく。

さてここからが実際のコーディング。Twitterログイン用のルーティング定義は以下のような感じになる。ほぼドキュメントに書かれている通り。

Route::get('login', function() {
    if (Auth::check()) {
        return Redirect::to('/')->with('message', 'ログイン済みです。');
    }
    $tokens = Twitter::oAuthRequestToken();
    Twitter::oAuthAuthorize(array_get($tokens, 'oauth_token'));
    die;
});

これで/loginにアクセスすると、Twitterの認証ページにリダイレクトする。Twitter側で認証されると呼ばれるコールバックURLは、dev.twitter.comで設定できるので、サンプルアプリの「/login/callback」に設定しておく。コールバック時の処理は以下のようになる。

Route::get('login/callback', function() {
    $token = Input::get('oauth_token');
    $verifier = Input::get('oauth_verifier');
    $accessToken = Twitter::oAuthAccessToken($token, $verifier);

    if (isset($accessToken['user_id'])) {
        $user_id = $accessToken['user_id'];
        $user = User::find($user_id);
        if (empty($user)) {
            $user = new User;
            $user->id = $user_id;
        }
        $user->screen_name = $accessToken['screen_name'];
        $user->oauth_token = $accessToken['oauth_token'];
        $user->oauth_token_secret = $accessToken['oauth_token_secret'];
        $user->save();

        Auth::login($user);

        return Redirect::to('/');
    } else {
        return Redirect::to('login')->with('message', 'Twitter認証できませんでした。');
    }
});

コールバックされたパラメータからアクセストークンを取得し、TwitterユーザーID($accessToken['user_id'])が取れていればログイン成功。そのTwitterユーザーIDがusersテーブルに登録されていない場合は、新しいレコードとして登録する。

screen_name、oauth_token、oauth_token_secretに関しては、ログインするたびに新しい情報に更新する。

で、Auth::login($user)がポイントで、これによってそのユーザーでログインしている状態をシステム(セッション)に記憶させる。

今回はusersテーブルに直接Twitterのログイン情報を持たせたんで、Twitter認証情報から直接Userクラスのインスタンスを取得できた。

もしも、Twitter認証情報をusersテーブル以外に持たせたい場合(通常は自前のID、パスワードでログインしつつ、必要ならばTwitterでもログインできるようにする、など)でも、最終的にUserクラス(というかUserIntafaceが実装されたクラス)のインスタンスを取得してしまえば同様にログイン処理が行える。

擬似コードとしては、以下のような感じ。

// Twitter認証テーブルから該当行を取得
$twitterUser = TwitterUser::find($accessToken['user_id']);

// Twitter認証テーブルとリンクしているUserテーブルの該当行を取得
$user = User::find($twitterUser['user_id']); 

Auth::login($user); // 該当ユーザーをログインさせる

一応ログアウト処理のルーティングも書いておく。これはTwitter認証とは関係なく、Laravel 4のログアウト処理のまま。

Route::get('logout', function() {
   Auth::logout();
   return Redirect::to('/')->with('message', 'ログアウトしました。');
});

あと、ホーム画面でログイン状況がわかるように書いておく。

Route::get('/', function()
{
    return View::make('home');
});
// app/views/home.blade.php
@if (Auth::check())
    {{{ Auth::user()->screen_name }}}ログイン中
    <a href="/logout">ログアウト</a>
@else
        未ログイン
    <a href="/login">ログイン</a>
@endif

未ログイン状態の場合は、ログインへのリンクを表示。ログイン中の場合は、ログインユーザーのscreen_nameを表示し、ログアウトリンクを表示している。

ちなみにこのTwitter認証パッケージは、Twitter APIパッケージを利用しているので、このままTwitter APIの機能を利用できる。たとえば、

$user = Auth::user();
Twitter::setOAuthToken($user->oauth_token);
Twitter::setOAuthTokenSecret($user->oauth_token_secret);
$timeline = Twitter::statusesUserTimeline($user->id);

なんて感じで、ユーザーのタイムラインを取得できる。

Laravel 4の認証処理周りはシンプルで、自前でいろいろ処理したい場合でも、なかなか使いやすそうだね。

Published At2013-07-11 17:34Updated At2020-01-01 15:44

技術日記PHPZendFramework
Zend_View_Filterで文字コード変換するときの問題Edit

前提条件

ZendFrameworkで、内部エンコーディングとしてUTF-8を利用しているWebアプリケーションを構築していて、一部ページのみガラケー(SHIFTJIS)対応したい。

問題

ZendViewFilterでUTF-8からSHIFT_JIS変換するフィルターを作り、ガラケー用コントローラのinitで
[sourcecode language="php"]
public function init()
{
  $this->view->addFilter('文字コード変換フィルター名');
}
[/sourcecode]
とかしてみた。すると、一見ちゃんと文字コード変換がかかったかと思いきや、一部文字化けしている。

調べてみると、addFilterしたフィルターはZendViewがレンダリング処理を走らせるたびに実行されるんで、レイアウトとかを使って内部で複数回ZendView::renderされてしまうと、その部分で複数回文字コード変換がかかる→文字化けする、ということらしい。

ZendFrameworkで文字コード変換 - slumbersでは、ZendLayoutControllerPluginlayoutを拡張したクラスで出力時に変換をかけたらうまくいったよって話が書いてあるけど、もうちょい楽な方法ないかなー。

解決策

複数回フィルターがかかるのがいやなら、出力する文字列が最後まで確定した後にフィルターがかかればいいんじゃないか、ということで、ガラケー用のレイアウトファイルの一番最後に、
[sourcecode language="php"]

....(省略)
<?php echo $this->layout()->content; ?>
....(省略)

<?php $this->addFilter('文字コード変換フィルター名');
[/sourcecode]
とかしてみた。うまくいった。

Published At2011-11-30 21:52Updated At2019-12-30 15:06