FuelPHP

FuelPHPで特定のページだけprofilingをfalseにする

FuelPHP1.5.2の話題です。

FuelPHPで特定のページだけprofilingをfalseにするにはControllerに以下のように記述すればOKです。

Fuel\Core\Fuel::$profiling = false;

正しいやり方は
Config::set('profiling', 0);
と書くことではないか?と思ったのですが、NGでした。

Fuel\Core\Fuelのinit関数に
static::$profiling = \Config::get('profiling', false);
と書いてあるので、最初にstatic::$profilingにconfig.phpに書いた設定が埋め込まれます。

CodeProofilerを表示するしないの分岐はfinish関数に
if (static::$profiling)
{
  CodeProofilerするコード
}
なif文があります。

ControllerはFuelのinit関数が呼ばれた後に呼ばれる…と思う…のでControllerで
Config::set('profiling', 0);
しても遅いです。

幸い$profilingはpublicで宣言されているので、Controllerに
Fuel\Core\Fuel::$profiling = false;
と書けばOKです。

※せっかくConfig::set()という関数があるんだから、Controllerから設定を変えられるべきじゃないかなあ…と思いますが、どうなんでしょう…

FuelPHPでheaderを送る

キャッシュさせたくないWebページは、HTTPのヘッダに
Pragma: no-cache
等を加える必要があります。
キャッシュについては[Studying HTTP] HTTP Cachingの文書がよいかなと思います。
※googleでキャッシュについて検索するといろいろ出てきて、どれが正しいのかわからなくなってきます。googleの検索は便利ですが、「正しくて」「体系的な」文書に出会うのは難しいと思います。

生のPHPの場合は、header関数を使えばOKでした。
FuelPHPの場合は、Responseクラスでheaderの中身を作って、send_headers()でhaederを送信します。
FuelPHPのマニュアルはResponse - Classes - FuelPHP Documentationになります。

具体的にはコントローラーに以下のように書くことになるかと思います。
※以下はFurlPHPで作ったWebアプリ「ブクスタ」のソースの一部です。

class Controller_Edit_Section_Book extends Controller_Edit_Section
{
  public function action_arrangement()
  {
    中略
 
    //キャッシュしないよう設定
    $response = new Response();
    $response->set_header('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate');
    $response->set_header('Expires', 'Mon, 26 Jul 1997 05:00:00 GMT');
    $response->set_header('Pragma', 'no-cache');
    $response->send_headers();

    //描画
    $this->template->set_global('title', '売り場に本を配置');
    $this->template->set_safe('css',array(Asset::css('edit/section/book/arrangement.css')));
    $this->template->contents = View::forge('edit/section/book/arrangement/contents', $data);
    $this->template->contents->set_safe('resultErrorMessage', $safeData['resultErrorMessage'] ); 
  }
}

クライアントに送られるヘッダーは以下のようになります。
Cache-Control:no-cache, no-store, max-age=0, must-revalidate
Expires:Mon, 26 Jul 1997 05:00:00 GMT
Pragma:no-cache

FurlPHPの公式マニュアルのsend_headers()の説明には、Note that you normally don't have to call this method manually. Fuel will take care of this as part of processing the request.と書いてあるので、send_headers()を使わないFurlPHP的に正しいやり方があるのだと思いますが、分かりませんでした…

 

FuelPHPはprofilingをtrueにするとEXPLAIN SELECTを実行する

FuelPHPはconfig.phpとdb.phpに以下のように設定するとCode Profilerに実行したSQLや実行にかかった時間を表示してくれます。

config.php
return array(
中略 
'profiling'  => true,
中略 
);

db.php
return array(
'default' => array(
中略
'profiling'    => true,
中略
),
);

profilingをtrueにしている状態でpostgresqlのログに以下のようなログが記録されていました。

nobody,ap2,192.168.0.15(53707),24877,2013-03-01 15:20:29.688 JST,42501,ERROR:  permission denied for relation access_log
nobody,ap2,192.168.0.15(53707),24877,2013-03-01 15:20:29.688 JST,42501,STATEMENT:  EXPLAIN SELECT 以下略

疑問が2つありました。
  1.  access_logテーブルにアクセスする場合は、ちゃんと$result = DB::query('SELECT * FROM access_log')->execute('root');な感じで、db.phpに設定したrootのユーザーでアクセスしているので、permission deniedとなるのはおかしい
  2. そもそもなぜEXPLAINが先頭にくるSQLを発行しているのかわからない
ソースを確認します。
\core\vendor\phpquickprofiler\phpquickprofiler.php
に以下の記述があります。
/*--------------------------------------------------------
    CALL SQL EXPLAIN ON THE QUERY TO FIND MORE INFO
----------------------------------------------------------*/

function attemptToExplainQuery($query) {
if (substr($query['sql'],0,6) == 'SELECT')
{
$rs = false;
try {
$sql = 'EXPLAIN '.html_entity_decode($query['sql'], ENT_QUOTES);
$rs = \DB::query($sql, \DB::SELECT)->execute();
}
catch(Exception $e)
{}

if($rs) {
$query['explain'] = $rs[0];
}
}
return $query;
}
 
profilingをtrueにしているとこの関数が実行されますが、execute();になっているので、rootでSQLが実行されずに、permission deniedになります。

では問題があるかというと、EXPLAINをつけて発行したSQLの実行結果を使っていない(表示していない)、profileがfalseになっている場合、この関数は実行されないので、特に問題ないかと思います。

ちなみに、途中経過をvar_dumpしてみると

array
  'sql' => string 'SELECT * FROM table' (length=95)
  'time' => float 10.288000106812
  'explain' => 
    array
      'QUERY PLAN' => string 'Aggregate  (cost=1.12..1.13 rows=1 width=0)' (length=43)

な感じで、PostgreSQLでEXPLAINした結果の最初の一行だけ取得していますが…表示するならEXPLAINで取得した結果全部を表示しないと意味なあ…と思いました。


FuelPHPをサブフォルダにインストールする場合の.htaccessの設定について

インストール方法 - インストール - FuelPHP ドキュメントにサブフォルダにインストールする場合という手順があります。

自分がFuelPHPをインストールするときに「サブフォルダにインストールする場合」に合致すると思っていなかったので記録します。

「サブフォルダにインストールする場合」というのは、

例えば
http://erogamescape.dyndns.org/fuelphp/
をトップページにしたいという場合です。

fuelphpを
/home/guest/fuelphp
に格納した場合、Apacheの設定に

Alias /fuelphp /home/guest/fuelphp/public

を書くかなと思います。

こうすると、
http://erogamescape.dyndns.org/fuelphp/
は見られるのですが、例えば
http://erogamescape.dyndns.org/fuelphp/user
とすると

Not Found
The requested URL /home/guest/fuelphp/public/index.php/user was not found on this server.

となります。これを解決するために公式ドキュメントに書いてある通り

.htaccess ファイルをエディタで開き、RewriteRule ^(.*)$ /[サブフォルダのディレクトリ]/index.php/$1 の部分を変更します。

をする必要があります。
書き換えないといけない箇所は3箇所です。

        # deal with php5-cgi first
        <IfModule mod_fcgid.c>
                RewriteRule ^(.*)$ /fuelphp/index.php?/$1 [QSA,L]
        </IfModule>

        <IfModule !mod_fcgid.c>

                # for normal Apache installations
                <IfModule mod_php5.c>
                        RewriteRule ^(.*)$ /fuelphp/index.php/$1 [L]
                </IfModule>

                # for Apache FGCI installations
                <IfModule !mod_php5.c>
                        RewriteRule ^(.*)$ /fuelphp/index.php?/$1 [QSA,L]
                </IfModule>

        </IfModule>

ご自身の該当する環境が分かればピンポイントで一箇所書き換えればよさそうです。

別解として、多分
 RewriteBase  /fuelphp
を追加するでもいいと思いますが自信がないです。
 

 

FuelPHPのPaginationのデザインをbootstrapに対応させる

FuelPHP Ver1.5.1の話題です。

FuelPHPはデフォルトでbootstrapを採用しているのですが、なぜかPaginationのデザインはbootstrapのCSSが適用されるようなタグではないのがデフォルトになっています。

bootstrapのpagenationは<ul>タグを使用する必要がありますが、FuelPHPのpaginationクラスが出力するタグはデフォルトで<span>になっています。

bootstrapに対応するように<ul>タグで出力するにはconfigに以下のように設定します。

$config = array(    中略
'name' => 'bootstrap', ); $pagination = Pagination::forge('mypagination', $config);


どんなタグで出力するかは、/core/config/pagination.phpに書いてあります。
タグ等を変更する必要がある場合は、このファイルを/app/config/にコピーして変更します。

FuelPHP のクエリ文字列でのページネーション - A Day in Serenity @ kenjisとほぼ同じ内容なのですが、具体的な設定方法('name' => 'bootstrap'と書くだけなのですが…)が分からなかったのでメモします。

 

FuelPHPのForm::inputの第二引数は自動的にhtmlentitiesが適用されます

FuelPHP Ver1.5.1の話題です。

FuelPHPのForm::inputの第二引数は自動的にhtmlentitiesが適用されます。

ですので、 
<?php echo Form::input('name', Input::post('name')); ?>
とInput Classの値をそのまま渡しても大丈夫です。

ソースは以下のようになっています。
\core\form.php
public static function input($field, $value = null, array $attributes = array())
{
return static::$instance->input($field, $value, $attributes);
}

\core\form\instance.php
public function input($field, $value = null, array $attributes = array())
{
if (is_array($field))
{
$attributes = $field;
! array_key_exists('value', $attributes) and $attributes['value'] = '';
}
else
{
$attributes['name'] = (string) $field;
$attributes['value'] = (string) $value;
}

$attributes['type'] = empty($attributes['type']) ? 'text' : $attributes['type'];

if ( ! in_array($attributes['type'], static::$_valid_inputs))
{
throw new \InvalidArgumentException(sprintf('"%s" is not a valid input type.', $attributes['type']));
}

if ($this->get_config('prep_value', true) && empty($attributes['dont_prep']))
{
$attributes['value'] = $this->prep_value($attributes['value']);
}
unset($attributes['dont_prep']);

if (empty($attributes['id']) && $this->get_config('auto_id', false) == true)
{
$attributes['id'] = $this->get_config('auto_id_prefix', 'form_').$attributes['name'];
}

$tag = ! empty($attributes['tag']) ? $attributes['tag'] : 'input';
unset($attributes['tag']);

return html_tag($tag, $this->attr_to_string($attributes));
}

public function prep_value($value)
{
$value = \Security::htmlentities($value, ENT_QUOTES);

return $value;
}
 


ControllerからViewに渡す値はset_safe等で渡さない限り、自動的にhtmlentitiesが適用されます。

Controllerで
$data['name'] =  Input::post('name');
View::forge('user/contents', $data);

Viewで
 <?php echo Form::Input('name', $name); ?>

とすると、Viewにnameが渡されたときにhtmlentitiesが適用され、Form::Inputに渡されたときにまたhtmlentitiesが適用されてしまいます。
しかし、 FuelPHPのデフォルト設定は「double_encodeをしない」なので、一度HTMLエンティティに変換された文字が再度変換されることはないので、特殊なケースを除いて問題ないです。

特殊なケースとは「&amp;」等を入力した場合です。
こちらについては、前の記事を参照ください。

※「&amp:」等が入力されることがないのであればいい気もするんですが、例えば「&は&amp;に変換されます」という記事を書きたいとかいう場合は困る気がしました。


FuelPHPのconfig.phpのhtmlentities_double_encodeはtrueにした方がいいと思います

FuelPHPのVerが1.5.1の話題です。

結論から書くとconfig.phpの設定は
'htmlentities_double_encode' => true,
'uri_filter'       => array(),
がいいんじゃないかなと思います。
※こうしたときにFieldsetクラスがうまく動かないかもしれないです。
 ごめんなさい、試してません。


FuelPHPのhtmlentitiesの第四引数(double_encode)のデフォルトはfalseです。
ちなみにPHPのデフォルトはtrueです。

FuelPHPのソースは以下のようになっています。
\core\classes\security.php
public static function htmlentities($value, $flags = null, $encoding = null, $double_encode = null)
{
static $already_cleaned = array();

is_null($flags) and $flags = \Config::get('security.htmlentities_flags', ENT_QUOTES);
is_null($encoding) and $encoding = \Fuel::$encoding;
is_null($double_encode) and $double_encode = \Config::get('security.htmlentities_double_encode', false);

以下略 


double_encodeの意味は以下の通りです。

double_encode

double_encode をオフにすると、PHP は既存の html エンティティをエンコードしません。 デフォルトでは、既存のエンティティも含めてすべてを変換します。


何を言っているかというと、double_encodeがtrueの場合は、「&amp;」と入力があったら「&amp;amp;」となりますが、falseの場合は、「&amp;」と入力があったら「&amp;」のままとするということです。

FuelPHPの場合、
 <?php echo Form::input('name', Input::post('name')); ?>
のように書いてフォームを出力します。
$_POST['password']の値が「&amp;」だと、本当は「&amp;amp;」として出力しないといけないのに「&amp;」と出力してしまうので、フォームのinputには「&」と表示されてしまいます。

&amp;や&gt;を入力する機会はほぼない気もいたしますが、入力されるケースがある場合、なぜ&amp;と入力した結果が&になってしまうか気がつかないと思います。

FuelPHPのdouble_encodeはconfig.phpで変更可能です。
config.phpのhtmlentities_double_encodeはtrueにした方がいいと思います 。

この変更に伴って
//'uri_filter'       => array('htmlentities'),
'uri_filter'       => array(),
としないといけません。

FuelPHP でのセキュリティ対策(1) - A Day in Serenity @ kenjisのURI フィルタの記載の通りで、uri_filterにhtmlentitiesを適用されているので、&等を含むURLは自動的にhtmlentitiesが適用されて、コントローラーで受け取った時点で、HTML エンティティに変換されてしまいます。
これをViewに渡すと、htmlentities_double_encodeがtrueの場合、もう1回htmlentitiesが適用されてしまいます。

※FuelPHPの思想は、URLをコントローラーで受け取った時点で、&等がHTML エンティティに変換されているべきだ、ということだと思うのですが、kenjisさんがFuelPHP Forumsで質問しているとおり、私もデフォルトの uri_filter の必要性がわかりません…


 

FuelPHPのEmailパッケージを使ってエンコードがISO-2022-JPのメールを送る場合、subjectはmb_encode_mimeheaderしなくてよいです

最新のFuelPHP(自分がソースを見たのは1.5.1です。)のEmailパッケージを使ってエンコードがISO-2022-JPのメールを送る場合、subjectはmb_encode_mimeheaderしなくてよいです。

前提はemail.phpの設定を以下のようにしている場合です。

return array(
'defaults' => array(

中略

'charset' => 'ISO-2022-JP',
'encodeing' => '7bit',

中略

);
);

本やWebのドキュメントを見ながら「ISO-2022-JPでメールを送りたい」設定すると、上記のように設定するかなと思います。

その後、古いドキュメントを見てしまうと、メールを実際に送信する部分のソースは以下のように書かれていると思います。
早川聖司 (2012) FuelPHP入門 株式会社ソーテック社も該当します。

$email = Email::forge();
$email->from( 'foo@example.com', 'foo' );
$email->to( 'bar@example.com' );
$email->subject( mb_encode_mimeheader( '題名', 'ISO-2022-JP' ) );
$email->body( mb_convert_encoding( '内容', 'ISO-2022-JP' ) );
$email->send();

FuelPHP1.5のEmailパッケージのソース(\package\email\driver.php)にsubject関数は以下のように書いてあります。

public function subject($subject)
{
if ($this->config['encode_headers'])
{
$subject = $this->encode_mimeheader((string) $subject);
}
$this->subject = (string) $subject;

return $this;
}

エンコードはsubject関数がやってくれますので、以下のように書くのが正解です。
$email = Email::forge();
$email->from( 'foo@example.com', 'foo' );
$email->to( 'bar@example.com' );
$email->subject(  '題名' );
$email->body( mb_convert_encoding( '内容', 'ISO-2022-JP' ) );
$email->send();

FuelPHPでメール送信(smtp) : てるてる坊主によると、FuelPHP1.2からmb_encode_mimeheaderが不要になったようです。
 

FuelPHP1.5のpaginationにQuery Stringによるページネーションが追加されています

Pagination: Now support pagination using a Query String variable.
ということで、FuelPHP1.5にpaginationにQuery Stringによるページネーションが追加されています。

※1/23時点でFuelPHPの最新のVerは1.5.1です。FuelPHP » Blogsによるとit is strongly suggested you upgrade to 1.5.1とのことです…せっかくあげたばっかりなのに…coreのディレクトリだけ差し替えればいいのかな…

FuelPHP のクエリ文字列でのページネーション - A Day in Serenity @ kenjis
公式マニュアルに書かれている通りなのですが、この機能が追加されたのは

検索窓が設置されていて、検索結果を10件ごとに表示したい!という場合、URLは
http://hogehoge/hoge?serch=ほげほげ
な感じになると思うのですが、FuelPHPのページネーションのURLにGETパラメータが付加できない

な問題を解決するためだと思っています。

具体的な設定は以下のようになるかと思います。
    $pagination = Pagination::forge('mypagination', array(
        'total_items' => 100,
        'per_page' => 10,
        'uri_segment' => 'page',
    ));   

FuelPHP1.5では、pagination_urlを指定しないと、現在のurlを使います。
pagination_urlは指定しない使い方が自然になるのかなあと思います。

uri_segmentに数字ではなくて、任意の文字を設定すると、「任意文字=ページ数」なパラメータがurlにくっつきます。

例えば上記設定の場合、任意文字がpageなので、
http://hogehoge/hoge?serch=ほげほげ
の2ページ目は
http://hogehoge/hoge?serch=ほげほげ&page=2
になります。

※ちなみに
 http://hogehoge/hoge?serch=ほげほげ
 の2ページ目が
 http://hogehoge/hoge/2?serch=ほげほげ
な感じになるようにはできないと思います。

FuelPHPのPaginationClassはConfigurationの配列の順序を間違えるとうまく動かない

この話題はFuelPHP1.4に適応されます。

FuelPHPのPaginationClassは以下のように設定します。
$pagination = Pagination::forge('mypagination', array( 'pagination_url' => 'http://docs.fuelphp.com/', 'total_items' => 35, 'per_page' => 5, 'uri_segment' => 2, ));
※これは公式サイトのPagenation ClassのHow toに書いてある通りの設定です。

これを以下のように書くとちゃんと動きません。 
$pagination = Pagination::forge('mypagination', array( 'pagination_url' => 'http://docs.fuelphp.com/', 'uri_segment' => 2, 'total_items' => 35, 'per_page' => 5, ));
※これは公式サイトのPagenation Classforgeに書いてある通りの設定です。

違いは配列の順番です。
前者はuri_segmenttotal_itemsper_pageの上にあります。

後者の設定で $pagination->render()すると、5ページ目からおかしくなります。
$paginationをvar_dumpすると、5ページ目ですから、current_pageがint 5でなくてはいけないのに、float 4になってしまいます。

原因はpagination classにおいて、uri_segmentotal_itemsper_pageを内部変数にすべてセットしてからtotal_pagesを計算しなければいけないのに、_set関数で1つ値をセットするたびに_recalculate関数でtotal_pagesを計算してしまっていて、per_pageはpagination class内部においてデフォルトで10という値が設定されているのですが、total_pagesを10で計算してしまった結果、おかしくなっています。

ええと…修正方法ですがpagination classは後から値をセットするようにできていないので

\core\classes\pagination.php
public function __set($name, $value = null)
{
//中略 

// update the page counters
//$this->_recalculate(); ←ここをコメントアウト
}

public function __construct($config = array())
{
// update the instance default config with the data passed
foreach ($config as $key => $value)
{
$this->__set($key, $value);
}    
 
  $this->_recalculate(); ←これを追加
    


でいいかなと思います。
または、total_itemsを配列の一番先に書くのでもいいと思います。

もしかしたら1.5以降で直っているかもしれませんが、はまる方がいらっしゃるかもしれませんので、記録しておきます。

記事検索