忍者ブログ
[PR]
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。



2025/01/16 00:41 |
Symfony1.2 10日目
ずいぶん久しぶりに再開。

投稿フォームを作成します。
通常の投稿フォームでは、まず入力画面を作成し、入力のバリデーションを作成し、保存を行うといった流れで開発すると思います。
Symfonyでは先に投稿フォーム用のクラスを作成し、画面描写もバリデーションもそちらに任せてしまうことになります。

Symfonyのフォームは、画面上の入力欄を決定するヴィジェットと、バリデーションを行うバリデータの二つに分かれています。
フォームは基本的にデータベースと直結しており、データベースを作成したときにフォームの基礎も一緒に作成されています。
lib/form/doctrine/base/BaseJobeetJobForm.phpを見てみると、BaseJobeetJobForm::setWidgets()でヴィジェットが、BaseJobeetJobForm::setValidators()でバリデータが定義されているのがなんとなくわかります。


とりあえずデフォルトのフォームを見てみます。
まずはこれまで機能していなかった「POST A JOB」リンクを編集。

apps/frontend/templates/layout.php
1
<a href="<?php echo url_for('@job_new') ?>">Post a Job</a>

ルーティングの一番上にあるjob:class:sfDoctrineRouteCollectionに引っかかり、/job/newへのリンクに変化します。
リンクを辿るとjobActions::executeNew()が実行されます。

中身は前作成したこれだけです。

apps/frontend/modules/job/actions/actions.class.php
1
2
3
4
<?php
    public function executeNew(sfWebRequest $request){
      $this->form = new JobeetJobForm();
    }

$formにJobeetJobFormクラスをそのまま突っ込んでいます。
setTemplate()が無いのでデフォルトのnewSuccess.phpが適用され、echo $formでJobeetJobFormの全てが表示されます。


これからデフォルトのフォームを改造していきます。
フォームを触る場合はlib/form/doctrine/base/BaseJobeetJobForm.phpではなくlib/form/doctrine/JobeetJobForm.phpを利用し、これらの初期値を上書きしていく形でフォームを整えていきます。

その前にとりあえず修正前のフォームを見てみると、Category idがテキスト入力欄ではなくカテゴリ名選択になっています。
スキーマではcategory_id:{ type: integer, notnull: true }となっており明らかにテキストなのですが、勝手にリレーションを辿ってJobeetCategoryに行き着くようです。
そこまではまあいいのですが、何処にもjobeet_category.nameを表示せよみたいなことを書いた覚えはないのですが、一体何処からどうやってjobeet_job.category_idjobeet_category.nameを結び付けると判断したのかさっぱりだ。


デフォルトでは全てのカラムが表示されるようになっています。
どのような入力欄となるかはスキーマから自動的に決められます。
主キーであるidはhiddenとなり、varchar型であるtypeやcompany等はtextとなり、bool型はチェックボックスとなり、datetime型は年月日時分選択フォームとなります。
それは便利ですが、created_atやexpires_atなんかはシステム側で自動的に登録させたいので入力欄を表示させたくありません。
フォームから入力欄を削除したい場合、単にフォームクラスからフィールドを削除します。

lib/form/doctrine/JobeetJobForm.class.php
1
2
3
4
5
6
7
8
9
10
11
12
13
<?phpclass JobeetJobForm extends BaseJobeetJobForm{
  /*
   * 初期設定
   */
  public function configure(){
      //不要な入力欄を表示しない
      unset(
      $this['created_at'], $this['updated_at'],
      $this['expires_at'], $this['is_activated']
    );
  }}

バリデータもスキーマから自動的に決定されます。
jobeet_job.companyはスキーマ上ではemail:{ type: string(255), notnull: true }となっており、バリデータにも予め「最大255文字」というバリデーションが設定されています。
単純なバリデーションであればこのままで問題無いですが、もっと複雑なバリデーションを行いたい場合もあります。
jobeet_job.emailはメールアドレスの登録欄ですが、現状ではjobeet_job.company同様255文字以内というバリデータしか設定されていません。
バリデータをメールアドレス形式のみを受け付ける形に差し替えます。

lib/form/doctrine/JobeetJobForm.class.php
1
2
3
4
5
<?phppublic function configure(){
  //メアドのバリデータ
  $this->validatorSchema['email'] = new sfValidatorEmail();
}

Symfonyには多くのバリデータが用意されており、基本的にバリデータクラスを選択するだけでバリデーションを行うことができます。
sfValidatorEmailはメールアドレス形式のバリデーションを行うことができます。
まあ、'...@a.aa'みたいなRFC違反のメールアドレスも通してしまうアバウトなものですが。

上記のバリデータはBaseJobeetJobFormで定義されているバリデータを上書きしています。
元々あった255文字以内というバリデータは消滅してしまうのです。
sfValidatorAndクラスを使うことで、一つのフィールドに複数のバリデータを定義することができます。

lib/form/doctrine/JobeetJobForm.class.php
1
2
3
4
5
6
7
8
9
10
<?phppublic function configure(){
  //メアドのバリデータ
  $this->validatorSchema['email'] = new sfValidatorAnd(
    array(
       $this->validatorSchema['email']
      ,new sfValidatorEmail()
    )
  );
}

元のバリデータにsfValidatorEmailをプラスしています。
これで「255文字以内、かつメールアドレス形式」というバリデーションが完成しました。
残念なことにフォームタグにmaxlength属性は付加されませんが、255文字以上の文字列を突っ込むとバリデーションエラーが発生し、エラーメッセージが表示されます。


現在jobeet_job.typeはvarchar型ですが、ここを特定の文字列しか入れられないようにしたい、というバリデーションを作成してみます。
特定の文字列しか許されないという具体的な内容はバリデータではなくテーブルに持たせるべきですので、JobeetJobTableモデルに記述する必要があります。
現在のJobeetJobTableに以下を追加。

lib/model/doctrine/JobeetJobTable.class.php
1
2
3
4
5
6
7
8
9
<?phpstatic public $types = array(
  'full-time' => 'Full time',
  'part-time' => 'Part time',
  'freelance' => 'Freelance',
);
public function getTypes(){
  return self::$types;
}

フォームのヴィジェットをデフォルトのテキストからラジオボタンに変更。

lib/form/doctrine/JobeetJobForm.class.php
1
2
3
4
5
<?php$this->widgetSchema['type'] = new sfWidgetFormChoice(array(
  'choices'  => Doctrine::getTable('JobeetJob')->getTypes(),
  'expanded' => true,
));
sfWidgetFormChoiceはラジオボタン、もしくはチェックボックスを意味するクラスです。
引数は配列になり、'choices'に具体的な選択肢を配列で与えます。
'multiple'及び'expanded'の引数を与えるとドロップダウンリストやチェックボックス等に変化します。

今変更したのはヴィジェットだけなので、選択肢以外の値を無理矢理送ったとしてもバリデーションを通過してしまいます。
バリデータもヴィジェットに合わせて修正する必要があります。

lib/form/doctrine/JobeetJobForm.class.php
1
2
3
4
<?php$this->validatorSchema['type'] = new sfValidatorChoice(array(
  'choices' => array_keys(Doctrine::getTable('JobeetJob')->getTypes()),
));

ヴィジェットと違うところとしてarray_keys()に注意が必要ですが、これで簡単にヴィジェットとバリデーションを合わせることが出来ました。
選択肢以外の値を送るとバリデーションエラーが発生するようになりました。


jobeet_job.logoは、ロゴ画像をアップロードした場合、そのファイル名を格納する場所になるということだそうです。
ということでファイルアップロード用のヴィジェットとバリデータを設定する必要があります。

まずアップロードしたファイルを置く場所を設置します。
web/uploads/jobsディレクトリを新規作成し、Apacheに書き込み権限を与えます。

ヴィジェットをファイルアップロードフォームに変更。

lib/form/doctrine/JobeetJobForm.class.php
1
2
3
4
<?php$this->widgetSchema['logo'] = new sfWidgetFormInputFile(array(
  'label' => 'Company logo',
));

ファイルのアップロード用フォームが設定されます。
また、このヴィジェットを設定すると<form> に自動的にenctype="multipart/form-data"が追加されます。

バリデータを追加。

lib/form/doctrine/JobeetJobForm.class.php
1
2
3
4
5
6
<?php$this->validatorSchema['logo'] = new sfValidatorFile(array(
  'required'   => false,
  'path'       => sfConfig::get('sf_upload_dir').'/jobs',
  'mime_types' => 'web_images',
));
これでファイルのアップロードフォームが完成しまし……エラーになりました。

> 500 | Internal Server Error | InvalidArgumentException
> This form is multipart, which means you need to supply a files array as the bind() method second argument.

jobActions::processForm()内でフォームに投稿された値をバインドしていますが、現状は以下のようになっていました。

apps/frontend/modules/job/actions/actions.class.php
1
2
3
4
5
6
7
8
<?php
  protected function processForm(sfWebRequest $request, sfForm $form){
    $form->bind($request->getParameter($form->getName()));
    if ($form->isValid()){
      $jobeet_job = $form->save();
      $this->redirect('job/edit?id='.$jobeet_job->getId());
    }
  }

一方チュートリアル的にはこうなっているはずだったみたいです。

1
2
3
4
5
6
7
8
9
10
11
<?phpprotected function processForm(sfWebRequest $request, sfForm $form){
  $form->bind(
    $request->getParameter($form->getName()),
    $request->getFiles($form->getName())
  );
  if ($form->isValid()){
    $job = $form->save();
    $this->redirect($this->generateUrl('job_show', $job));
  }}

こんなところ触った覚えないんだが何故違うんだろう?
ファイルアップロードフォームは他のフォームと内容がかなり違うので、バインドもファイルだけ独立して行うことになっているようです。
jobActions::processForm()をチュートリアルに合わせて修正。


>バリデーターはデータベースに相対パスを保存するので、showSuccessテンプレートで使われているパスを次のように変更します

4日目で既に全く同じ記述にしてあるので、この節は意味がわかりません。

この時点で試しに投稿してみると、投稿が正しく処理されフォームから削除した値も正しく登録され画像も保存され投稿内容と画像をフロントから閲覧することが出来ます。
うひょお、気持ちわりい。


実は10日目までは1.2のころに作成していたりするので1.4の作りと違いがありますが気にしない。
ていうか、これ書いたころはJobeetJobPeerなんて影も形もなかったはずなんだが…?


PR


2010/10/22 23:33 | Comments(0) | TrackBack() | PHP
ZF1.10 Zend_Cache
Zend_Cacheとは。

http://framework.zend.com/manual/ja/zend.cache.introduction.html
> Zend_Cache は、任意のデータをキャッシュするための一般的な手法を提供します。

なんだそれは。
ドキュメントの説明は長々と書いてあって丁寧は丁寧なのですが、長すぎて読むのがしんどいです。
簡単に言うとSmartyキャッシュで、要はob_start()、ob_end_clean()、file_put_contents()、file_get_contents()あたりを纏めてパッケージングしたようなものとなります。

とりあえず簡単な例を作ってみます。
ちなみにリファレンスでは最初にZend_Cache::start()Zend_Cache::end()が紹介されていますが不要なので忘れましょう。

zend_cache1.php
<?php
	//Zend_Cache
		require_once('Zend/Cache.php');
	
	//フロントエンドはCore(文字列のみ)を使用
		$zf_cache_frontend = 'Core';
	//フロントエンドオプション
		$zf_cache_frontend_option = array(
			//キャッシュの有効期限
			 'lifetime' => 30
			//シリアライズする
			,'automatic_serialization' => true
		);
	
	//バックエンドはFileを使用
		$zf_cache_backend = 'File';
	//バックエンドオプション
		$zf_cache_backend_option = array(
			//キャッシュを保存するディレクトリ
			'cache_dir' => './tmp/'
		);
	
	//インスタンス
		$zf_cache = Zend_Cache::factory($zf_cache_frontend, $zf_cache_backend, $zf_cache_frontend_option, $zf_cache_backend_option);
	
	//キャッシュを取得する
		$cache = $zf_cache->load('hoge');
		//キャッシュが取得できなければ
		if(!$cache){
			//キャッシュする内容を取得	本来はなんか重い処理
				$cache = 'abcde';
			//キャッシュに保存
				$zf_cache->save($cache, 'hoge');
		}
	
	//表示
		print($cache);

Zend_Cache::factory()でインスタンスを作って、save()で保存、load()で取得するというそれだけの話です。
今回は'hoge'だけですが、ここに一意のIDを指定することで、複数の内容を保存することもできます。

バックエンドはそのまま、保存先に何を使用するかということです。
今回は'./tmp'フォルダにファイルとして保存しています。

フロントエンドは何を保存するか、という意味になります。
Coreは最も単純なフロントエンドで、静的文字列を保存します。

フロントエンドオプションのautomatic_serializationはデフォルトではfalseで、その状態だと本当にただのテキストで保存されます。
さすがに文字列しか保存できないといくら何でも役に立たないのでtrueにしておきましょう。
serialize()できるものならなんでも保存できるようになります。


以上でキャッシュを利用できるようになりました。
最初に実行した際に、キャッシュする内容が取得されて保存されます。
その後30秒間は何度読み込んでも保存したキャッシュが読み込まれるため、具体的なロジックは実行されません。
有効期限が過ぎると、それまでのキャッシュは無かったものとなり、再びキャッシュする内容が取得されて保存されることになります。

今回は取得する部分がただの文字列だったのでまったくなんのありがたみもありませんでしたが、これが一回10秒かかるDBアクセスだったりしたらどうなるか。
かわりにsleep()を仕込んでみたりすると、どれだけ快適になるかがわかると思います。


さて、デフォルトだとフロントエンドオプションのautomatic_cleaning_factorによって10回に1回ガベージコレクタが起動するようになっているように読めるのですが、実際には行われません。
キャッシュファイルはいつまでも残り続けます。
セッションと違いファイル名は変わらないのでファイルが際限なく増えたりする心配はないのですが、削除を行うメソッドも用意されています。
	//特定のキャッシュを削除
	$zf_cache->remove('hoge');
	//有効期限切れのキャッシュを削除
	$zf_cache->clean(Zend_Cache::CLEANING_MODE_OLD);
	//全キャッシュを削除
	$zf_cache->clean(Zend_Cache::CLEANING_MODE_ALL);

ていうか、フロントエンドでガベージコレクタって一体何するんだ?
バックエンドじゃないのか?


2010/10/15 23:44 | Comments(0) | TrackBack() | PHP
ZF1.10 :file_get_contentsでZend_Http_Cookie
前回Zend_Http_Cookieでcookieを簡単に取得しましたが、file_get_contents()で実行することも可能です。

zend_http5.php
<?php
	
	$zend_http1='http://localhost/zend_http1.php';
	$zend_http2='http://localhost/zend_http2.php';

	//取得
		$file = file_get_contents($zend_http1);
	
	//HTTPレスポンスヘッダは$http_response_headerに入ってくる
		foreach($http_response_header as $key=>$val){
			//cookieを取得
			if(strpos($val, 'Set-Cookie:')!==false){
				$cookie_val = str_replace('Set-Cookie:', '', $val);;
				break;
			}
		}
	
	//ストリームコンテキスト作成
		$opts = array(
			'http'=>array(
				'method'=>'POST',
				'header'=>'Cookie:'.$cookie_val."\r\n"
			)
		);
		$context = stream_context_create($opts);

	//ストリームコンテキストを使用してfile_get_contents
		$res = file_get_contents($zend_http2, false, $context);

さほど難しくないといえばそのとおりなのですが、$http_response_headerとかstream_context_create()とかfile_get_contents()の第三引数とか、普通は使わないような関数を使わないといけないので少々面倒です。
あとcookieが無かった場合とか何も考えてない作りなのでそこらへんもどうにかする必要があります。


Zend_Http_Cookieまったく関係ないな。

 



2010/10/11 21:21 | Comments(0) | TrackBack() | PHP
XAMPP1.7.3 設定ファイルに定義されている管理ユーザ(controluser)での接続に失敗しました
http://localhost/phpmyadmin/
というURLでXAMPPにデフォルトで入っているphpMyAdminを利用できます。

それはいいのですがアクセスすると
設定ファイルに定義されている管理ユーザ(controluser)での接続に失敗しました

という謎エラーが。

ついでに
Your PHP parameter session.gc_maxlifetime is lower that cookie validity configured in phpMyAdmin, because of this, your login will expire sooner than configured in phpMyAdmin.

ってのも出てたので一緒にチェックしてみます。


まずDocumentRootにphpmyadminなんてディレクトリはありませんが、これは単純にxampp/apache/conf/extra/httpd-xampp.confでAliasが設定されています。
xampp/phpMyAdmin/あたりになっているはずですが、そこにphpMyAdminの本体が転がっています。
phpMyAdmin自体はMVC?何それ食い物?的な書かれ方をしてるので読むのは相当難儀です。

まず文言の定義はlang/japanese-utf-8.inc.phpです。
    $strControluserFailed = '設定ファイルに定義されている管理ユーザ(controluser)での接続に失敗しました';

で、次の$strControluserFailedはlibraries/dbi/mysqli.dbi.lib.phpです。
$GLOBALS…

PMA_DBI_connect()は都合二回呼ばれており、一回目の接続では失敗してtrigger_error()されています。
二回目の接続にはconfig.inc.phpで設定されている$cfg['Servers'][$i]['user']$cfg['Servers'][$i]['password']が使用されています。
ではこの一回目の接続にはどのパラメータが使用されているかというと、同じファイルの$cfg['Servers'][$i]['controluser']$cfg['Servers'][$i]['controlpass']です。

どゆこと?

名前からして$cfg['Servers'][$i]['user']には一般ユーザ、$cfg['Servers'][$i]['controluser']には管理者権限のユーザを割り当てるのかなとも思いますが、なんのためにこんな作りになっているのかはよくわかりませんでした。
$cfg['Servers'][$i]['controluser']をログインできる値にする、もしくは設定を削除することでエラーは発生しなくなります。


後者のエラーは、session.gc_maxlifetimeがcookieの有効期限より短いから意味がないぞ、というエラーです。
phpMyAdminは$cfg['LoginCookieValidity']でcookieの有効期限を設定します。
これが短すぎるとちょっと席を外しただけですぐログアウトされてしまい、不便になります。

で、それとは別にPHPではsession.gc_maxlifetimeでサーバ側セッションファイルの有効期限が設定されています。
この時間が過ぎたセッションファイルは廃棄処分されてしまいます。

結果として、session.gc_maxlifetime以上$cfg['LoginCookieValidity']以下の期間にアクセスされると、cookieは有効期限内だからブラウザはcookie値をちゃんと送ってくるのに、サーバ側のセッションファイルは削除されてしまっているので、セッションが継続できないよ、という状態になります。
実際のところは単にセッション期限切れになるだけなので特に問題はありません。
物凄い暇な人がcookieを解析して、有効期限が60分となっているのに50分後にアクセスしたらセッション期限切れになった!これはおかしい!とか言い出したりすることが万一あったりするかもしれないレベルの話です。

エラーが気持ち悪いならconfig.inc.phpの隅っこに
ini_set("session.gc_maxlifetime", $cfg['LoginCookieValidity']);
とか書いておけばいいです。


2010/10/08 23:37 | Comments(0) | TrackBack() | PHP
ZF1.10 :Zend_Http_ClientとZend_Http_Cookie
HTTPリクエストを送受信するクラスです。
送受信ならfile_get_contents()curl_exec()で十分だろ、と思いきやこいつらには重大な問題があります。

何処かにPOST送信を行った際、ステータスコード302が返ってくるとそっちにGETで送り直しちゃうんだよね。
http://www.studyinghttp.net/status_code#Code302

本来POSTで302が返ってきたらユーザに一旦確認したうえで移動先にPOST送信する、というのが本来の動きなのですが、上記関数やブラウザを含む大抵の実装が確認せずにGET送信してしまうようになっています。
この挙動を変更することができないのか探してみましたがよくわかりませんでした。

Zend_Httpもデフォルトでは上記のような挙動を取るようになっていますが、オプションで正しい動作を行うように設定することができます。

また、Zend_Http_Cookieを使うことでCookieを簡単に使い回すことができ、セッションを利用しているサイトへのリクエストも楽に行うことができます。
言葉で言っても意味がわかんないので書いてみましょう。

zend_http1.php
<?php
	session_set_cookie_params(0,'/','localhost');
	session_start();
	
	$_SESSION['a']='aaa';
	$_SESSION['b']='bbb';

zend_http2.php
<?php
	session_start();
	var_dump($_SESSION);

zend_http1.phpでセッションに値を代入し、zend_http2.phpでその値を表示します。
ブラウザで順にアクセスすると、正しくセッションの値

array(2) {
  ["a"]=>
  string(3) "aaa"
  ["b"]=>
  string(3) "bbb"
}

が表示されます。


ではPHPで順に取得してみましょう。
zend_http3.php
<?php
	file_get_contents('http://localhost/zend_http1.php');
	$ret=file_get_contents('http://localhost/zend_http2.php');
	
	var_dump($ret);

$bodyは、

array(0) {
}


何も取得できませんでした。
file_get_contentsはcookieとか特に考えないので、zend_http1.phpをfile_get_contentsした時点で発行されたcookieをzend_http2.phpに送信していないからです。
zend_http2.phpにcookieを送信する手段もなくはないですが、stream_context_create()なんかで自作する必要があり少々面倒です。
Zend_Httpでやってみましょう。

zend_http4.php
<?php
	
	$zend_http1='http://localhost/zend_http1.php';
	$zend_http2='http://localhost/zend_http2.php';
	
	//Zend_Http
		require_once('Zend/Http/Client.php');
		require_once('Zend/Http/Cookie.php');
		$http = new Zend_Http_Client();
	
	// 厳格なリダイレクト
		$http->setConfig(array('strictredirects' => true));
	
	//リクエスト送信先
		$http->setUri($zend_http1); 
	
	//リクエスト送信
		$http->request('POST');
	
	//返り値を取得
		$res=$http->getLastResponse();
		$header=$res->getHeaders();
	
	//Cookieを取得
		$cookie=Zend_Http_Cookie::fromString($header['Set-cookie']);
	
	//新たなリクエスト
		$http = new Zend_Http_Client();
		
	//リクエスト送信先
		$http->setUri($zend_http2); 
	
	//Cookieをセット
		$http->setCookie($cookie);
	
	//リクエスト送信
		$res=$http->request('POST');
	
	//レスポンスボディ
		$body=$res->getBody();
		
		var_dump($body);

リクエスト送信先はスキームからの入力が必須です。
相対リンクやホスト名からの表記などはできません。

レスポンスボディ$bodyは、

array(2) {
  ["a"]=>
  string(3) "aaa"
  ["b"]=>
  string(3) "bbb"
}


見事にセッション情報が表示されました。
$cookie=Zend_Http_Cookie::fromString($header['Set-cookie']);
$http->setCookie($cookie);

の部分において、一回目のリクエストで発行されたセッションキーを取得し、二回目のリクエストに付加しています。
これにより、簡単にセッションの継続を行うことができます。


前公開したしたらば削除スクリプトはZend_Httpのcookie制御とstrictredirectsを使用しています。
旧管理画面は画面遷移のたびに毎回パスワードをフォームで送るというどうなんだ仕様だったのですが、リニューアルを経てセッションを利用したログイン管理になりました。
セキュリティ上は正しいのですがスクリプト的にはcookieを使い回さないといけないため少々面倒になりました。
また、したらばの管理画面はフォーム送信のたびに毎回302でリダイレクトされる仕様のため、リダイレクト先にも正しくPOSTを送信するためにstrictredirectsを使用しました。
ただこれ、ブラウザは普通にGETで送信し直しているにも関わらず正しく動いてるのにPHPからだとGETでリダイレクトしても動かず、たまたまPOSTで再送信したところ動いたという状態で、原因がよくわからないのが困ったものなのですが。


2010/10/05 22:07 | Comments(0) | TrackBack() | PHP
ZF-:Zend_Debug

> Zend_Debug::dump() メソッドが、PHP の関数 ≫ var_dump() をラップしたものであることを理解すると有益でしょう。
ということでvar_dumpが少々見やすくなります。

中身はvar_dumpを出力制御で奪い取って正規表現で装飾してるだけという単純な内容でした。

クラス名を見て期待していたリフレクションなんかは全く行ってくれないようです。
オブジェクトなら勝手にリフレクションしてくれるようなvar_dump()を期待してたのに。残念。


ちなみにXdebugを使用していると、正規表現がうまくいかずvar_dump()と同じ出力になるので全く意味がありません。

他の機能に比べてどうにもショボいクラスだ。



2010/05/17 22:44 | Comments(0) | TrackBack() | PHP
Smartyのちょっと便利な予約変数

わざわざ$smarty->assign()しなくても、予め使用できる変数がいくつかあります。

$smarty.post
$smarty.get
$smarty.request
$smarty.cookies
$smarty.session
$smarty.server
$smarty.env

各スーパーグローバル変数及び環境変数。

$smarty.const
PHP定数。

$smarty.now
呼び出したその時点でのタイムスタンプ。
その時点でのタイムスタンプなので、二回呼び出すと違う時間になる。

$smarty.section
$smarty.foreach

ループ内で、ループ回数などを取得できる。

$smarty.ldelim
$smarty.rdelim

現在のデリミタ。
デフォルトは{}

$smarty.capture
{capture}でキャプチャした内容を表示。

$smarty.config
{config_load}で読み込んだ変数を表示。{$smarty.config.foo} はさらに{#foo#}でショートカット可能。

$smarty.template
テンプレート名、要するに自分自身のファイル名。

$smarty.version
Smartyのバージョン情報。


$smarty.requestとか知らなかった頃は毎回$smarty->assign('request',$_REQUEST);とかやってたわー。

$smarty自身は、{foreach}などに通しても中身を順に取得したりはできません。
 



2010/05/10 23:18 | Comments(0) | TrackBack() | PHP
OracleでLIMIT句

Oracleにlimitが存在しないというのは有名な話です。
おかげでページングなどが必要なアプリに対し、Oracleは恐ろしく使いにくいDBになってしまっています。

MySQLであれば
    SELECT * FROM table ORDER BY id DESC LIMIT 100,10
と簡単に書くことができますが、同じことをOracleで行おうとすると、
    SELECT * FROM ( SELECT * FROM table ORDER BY id DESC ) WHERE ROWNUM BETWEEN 100 AND 110
と副問い合わせを使わざるを得ません。

さてここで疑問なのですが、この副問い合わせ、一旦テーブルtableの全件を並べ替え、その中から該当の行を取り出してきています。
これって相当パフォーマンスがアレなんじゃないのと。
http://www.oracle.co.jp/forum/thread.jspa?threadID=2001064
やはりよろしくないようです。

実際の検索方法としては副問い合わせ以外にも分析関数、相関サブクエリによる検索方法というものがありました。
http://oraclesqlpuzzle.hp.infoseek.co.jp/10-3.html
相関サブクエリは、前から110件だけは並べ替えるけど、それ以降は全部111位とみなすことで並べ替えを行わず、それによって検索速度を上げるという手法だそうです。

まあ早くなるのはいいんですが、たかが並べ替えたいだけのためにこんな面倒なSQLを書かないといけないってどうなんだ。

WebアプリにはやはりMySQL一択ですな。
まあMySQLにもインデックス張ると遅くなるとか面白現象もあるみたいですが。

 



2010/05/03 21:01 | Comments(0) | TrackBack() | PHP
EC-CUBE PHP5.3.1でEC-CUBEを動かす
手元のPHPがそれまで5.2.9だったのですが、5.3.1にアップグレードしたところ死亡。

>Deprecated: Assigning the return value of new by reference is deprecated in C:\eccube\data\module\DB.php on line 475
>Deprecated: Assigning the return value of new by reference is deprecated in C:\eccube\data\module\DB.php on line 552
>Deprecated: Assigning the return value of new by reference is deprecated in C:\eccube\data\module\DB.php on line 1129
>Deprecated: Assigning the return value of new by reference is deprecated in C:\eccube\data\module\PEAR.php on line 557
>Deprecated: Assigning the return value of new by reference is deprecated in C:\eccube\data\module\PEAR.php on line 560
>Deprecated: Assigning the return value of new by reference is deprecated in C:\eccube\data\module\Mail.php on line 156
>Deprecated: Assigning the return value of new by reference is deprecated in C:\eccube\data\module\Net\UserAgent\Mobile.php on line 165
>Deprecated: Function ereg() is deprecated in C:\eccube\data\class\util\SC_Utils.php on line 83
>Deprecated: Assigning the return value of new by reference is deprecated in C:\eccube\data\module\DB\common.php on line 964
>Deprecated: Assigning the return value of new by reference is deprecated in C:\eccube\data\module\DB\common.php on line 1171


平たく言うと、「その関数、使い方は今後サポートしなくなるから使うな」ということです。
Pearでエラーってどういうことだよ、と見てみると更新日が2007年。
PHP4なんていい加減捨ててしまえ。

フォーラムでの解決方法は「ダウングレードしろ
いや駄目だろそれ。

後方互換性の確保という理念はまあいいのですが、そのために新バージョン切り捨てとかありえない。


手っ取り早く修正するのであればerror_reporting()C:\eccube\data\class/SC_Initial.phpに書かれていますので、

function setErrorReporting() {
  //error_reporting(E_ALL & ~E_NOTICE);
  error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
}


ってするといいのですがこんなのではよくありません。

とりあえずpearコマンドで入れられている共通版のPearをインクルードするように差し替えようと思いましたが、至る所で
    require_once(dirname(__FILE__) . '/../module/Mail.php');
とか書かれてる。
もうちょっとこう……

次善の策と言うことで直接書き換えます。
Pear関連で発生しているDeprecatedエラーは、全部 &newって書かれているせいなので、単&にを削除するだけです。

残りは\data\class\util\SC_Utils.phpereg()ですが、単に文字列比較しているだけなので
  //if( !ereg('/install/', $_SERVER['PHP_SELF']) ) {
    if( strpos($_SERVER['PHP_SELF'] , '/install/' )===false ) {

と書き換えます。

余談ですがこのSC_Utils::sfInitInstall()、場合によっては最終的に完成するURLが'hoge/fuga//install/'みたいになります。
header('Location: hoge/fuga//install/')であればhttp://eccube.localhost/hoge/fuga/install/に一応辿り着けるのですが、htmlディレクトリをDocumentRootにしている場合、header('Location: //install/')となりhttp://install/を探しに行って死にます。
最初の一回しか使わないからいいといえばいいんですけどねえ。

さて無事エラーは出なくなりましたが、かわりに真っ白です。
はて。

順に読んでいくと、

/html/install/index.php
require_once("../require.php");

/html/require.php
require_once($require_php_dir . HTML2DATA_DIR . "require_base.php");

/data/require_base.php
$sessionFactory->initSession();

/data/class/session/sessionfactory/SC_SessionFactory_UseCookie.php
session_start();
で死んでいました。
お?

どのタイミングで死ぬかというと、/data/require_base.php
$objSession = new SC_Helper_Session_Ex();
です。
これより前だとsession_start()に成功し、後だとsession_start()に失敗します。

SC_Helper_Sessionが何をやってるかというとsession_set_save_handler()を使ってセッションをDBに保存していました。

…データベースの設定はインストール開始後に行うんですが…
FatalErrorすら吐かずに完全停止というのは少々ご勘弁願いたい。

とりあえず/data/class/SC_Inittial.phpに以下の対応を行うことでフリーズは回避されます。
  //define("DEFAULT_DSN", "pgsql://nobody:password@localhost:5432/eccubedb")
    define("DEFAULT_DSN", "mysql://nobody:password@localhost:5432/eccubedb");


なんでだよ。

インストール開始後も、ことある毎にDeprecatedエラーが大量発生しまくって酷いったらありゃしない。
インストール終了後、サイトトップを表示してみると目眩がします。
なんで今更ereg()なんか使ってるんだ。

はっきりいって使えたものではないので、結局error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);にしてしまいました。


EC-CUBEの記事


2010/04/30 22:34 | Comments(1) | TrackBack() | PHP
EC-CUBE 商品管理画面、脅威の隠し機能

管理画面→商品管理→商品マスタに移動すると、以下のようなURLになると思います。
http://eccube.localhost/admin/products/index.php

ここでPOSTパラメータにmode=delete_allと記入して送信してみると、商品データが全部消えてしまいます。
怖ろしい。
実はロジック的にそういう機能が書かれているのに、テンプレートからリンクが貼ってないのですよね。

一体何のためにあるんだこの機能。

ていうか、いつも思うんだが$_GETとか$_POSTとか書かないで$_REQUESTにしてくれ。


EC-CUBEの記事
 



2010/04/26 22:58 | Comments(0) | TrackBack() | PHP

<<前のページ | HOME | 次のページ>>
忍者ブログ[PR]