読者です 読者をやめる 読者になる 読者になる

FuelphpのPHP Quick Profilerを特定のControllerだけ止める方法

FuelPHP PHP Quick Profiler

FuelphpにはPHP Quick Profiler(pqp)が付属しています。
すごく便利なのですが、JSONを扱う開発など特定のControllerに対して一時的に停止したいこともあるかと思います。

かなり無理矢理ですがやってみました。

まずクラスの作成

class Stopprofiler extends Profiler { public static function stop_profiling() { static::$profiler = null; } }

その後、該当のコントローラで、

Stopprofiler::stop_profiling();

を呼び出せば無事PQPがなくなっているかと。
仕組みは見たまんまです。
Profilerクラスの静的変数$profilerがprotectedなので、継承して書き換えただけです。
オブジェクト指向的には非常に問題のある方法ですが(;´Д`)

なにか他にいい方法があれば教えて下さい。

\Fuel::$profiling = false;

はてブ経由で指摘がありました。これでいいみたいです。

FuelPHPでGearmanを動かしてみた

FuelPHP Daemon Gearman

Gearmanとは?

Gearmanはジョブキューサーバの一つです。

時間のかかる処理等をバックグラウンドで処理する時や、分散処理を行いたい時に使うとではないかと。
他の使い方はわかりませんが、これだけでも十分利用価値があります。

使ってみる

インストール方法は他のサイトみて下さい。
以前にビルドしたものの、どうやったか覚えてないので(;´Д`)

参考:

TheSchwartzの代わりにGearman+libdrizzleでジョブキューサーバ作る - 個人事業主のつぶやき
ジョブキューサーバ Gearmand を PHP から使ってみた | tech.kayac.com - KAYAC engineers' blog

とりあえず自分はgearmandにlibdrizzleを組み合わせてMySQLにキューを保存しています。

Net_Gearmanの導入

http://pear.php.net/package/Net_Gearman/からではなく、
https://github.com/lenn0x/net_gearmanからダウンロードして下さい。

E_STRICTエラーが発生するので…

fuel/app/vendor/net_gearmanへ解凍。

Tasksを作成。Clientも追加

fuel/app/tasks/gearman.php

<?php

namespace Fuel\Tasks;

set_include_path(get_include_path() . PATH_SEPARATOR . APPPATH . 'vendor/net_gearman');
require_once 'Net/Gearman/Client.php';

class Gearman
{
    public function run()
    {
        \Daemon\Daemon::forge()->setCallback('\\Gearman_Worker::worker')->run();
    }
    
    public function workertest()
    {
        $client = new \Net_Gearman_Client('localhost:4730');
        $client->test(array('name'=>'world'));
    }
}

なお、前回のDaemonパッケージを利用しています。

Workerを作成

fuel/app/classes/gearman/worker.php

<?php

set_include_path(get_include_path() . PATH_SEPARATOR . APPPATH . 'vendor/net_gearman');
define('NET_GEARMAN_JOB_PATH', APPPATH . 'classes/gearman/job');
define('NET_GEARMAN_JOB_CLASS_PREFIX', 'Gearman_Job_');
require_once 'Net/Gearman/Worker.php';

class Gearman_Worker
{
    public static function worker()
    {
        $servers = array('localhost:4730');
        try {
            $worker = new Net_Gearman_Worker($servers);
            $worker->addAbility('test');
            $worker->beginWork();
        } catch (Net_Gearman_Exception $e) {
            new FuelException($e->getMessage());
        }
    }
}

Jobを作成

fuel/app/classes/gearman/job/test.php

<?php

class Gearman_Job_test extends Net_Gearman_Job_Common
{
    public function run($arg)
    {
        Log::error("Hello, " . $arg['name'] . "!");
    }
}

動かしてみる

$ php oil refine gearman
$ ps aux | grep php
harul     1939  0.0  0.1 136040  7532 ?        S    13:33   0:00 php oil refine gearman
harul     1940  0.0  0.2 136040  8056 ?        S    13:33   0:00 php oil refine gearman
harul     2967  0.0  0.0  15360   912 pts/0    S+   13:33   0:00 grep --colour=auto php

動いているようだ

clientを動かしてみる

$ php oil refine gearman:workertest

ログを確認してみる

Error - 2012-08-12 13:51:35 --> Hello, world!

無事動いた

めでたしめでたし

FuelPHPのデーモンパッケージ

FuelPHP Daemon

Webアプリを作っている時に、バックグラウンドで処理をしたい事があるかと思います。

今回、Gearmanのworkerを動かそうと思ったのですが、
Unix系OSPHPでは

php oil refine ほげほげ &

としても、うまく動きません。

そこで、デーモンを動かすようにバックグラウンドで処理を行うパッケージを作ってみました。

正直に言うと<fuelphpのバックグラウンドプロセスパッケージ>のパクリです。ただ、あちらはUnix系では動かないので…

設置

次の位置においてください
FuelPHP Daemon package
/fuel/packages/daemon
なお、このパッケージを動かすためには、PHPのpcntlとposixが有効になっている必要があります。

config.phpを編集

daemonをAPPPATH/config/config.php の always_load の部分に追加します

return array(
    'always_load' => array(
        'packages' => array(
            'daemon', // 追加
        ),
    )
);

使用例

<?php

namespace Fuel\Tasks;

class Daemontest
{

    public function run()
    {
        \Daemon\Daemon::forge()->setCallback('\\Fuel\\Tasks\\Daemontest::callback')->run();
    }

    public static function callback()
    {
        while(TRUE){
            //ここにいろいろな処理を書く
            sleep(1);
        }
    }
}

注意点とか

Windowsでは動きません

動作のしくみ

run()が呼ばれたときには下図プロセス1しかありません。

f:id:harul:20120811234120p:plain

まずプロセス1からfork()して子プロセス2の生成

f:id:harul:20120811234124p:plain

そのプロセス2をsetsid()にて新しいセッションに移す。

f:id:harul:20120811234126p:plain

その後、プロセス1は終了し、またプロセス2はfork()を行ってプロセス3を生成。

セッションリーダとなるプロセスが残っていると、端末に紐付することができてしまうので、
プロセス2は終了。

f:id:harul:20120811234129p:plain

最後に、プロセス3にてfork()を行い、子プロセス4にてcallbackの呼び出しを行っています。

この状態で、プロセス3にSIGTERMが呼ばれた場合には、
プロセス4にSIGINTを送っています。

書いてもいまいちわかりにくいですね。申し訳ない。

次はGearmanのパッケージを作ります。

FuelPHP TreeOrm Packageをより使えるようにした

FuelPHP Tree

前回に作成した、FuelPHPでもTree Behaviorをより使えるように改良しました。

準備

設置

次の位置においてください
FuelPHP TreeOrm Package
/fuel/packages/treeorm

指定のフィールドの確認

使用するテーブルに以下のフィールドが必要です

`id` int unsigned NOT NULL auto_increment,
`parent_id` int unsigned default '0',
`lft` int(10) default '0',
`rght` int(10) default '0',
PRIMARY KEY (`id`)

config.phpを編集

treeormとormをAPPPATH/config/config.php の always_load の部分に追加します

return array(
    'always_load' => array(
        'packages' => array(
            'orm', // 追加
            'treeorm', // 追加
        ),
    )
);

モデル毎の設定

該当のモデルにovserversの追加

use Orm\Model;

class Model_Testtree extends Model
{
    protected static $_properties = array(
        'lft',
        'rght',
        'parent_id',
    );

    protected static $_observers = array(
        'TreeOrm\\Observer_Tree' => array(
            'events' => array('before_save','before_delete'),
            'property' => array( 'left' => 'lft', 'right' => 'rght', 'parent_id' => 'parent_id', 'id' => 'id')
        ),
    );
}

これで、とりあえず使えます
ここまでは前回とほぼ一緒
もし、propertyがなかった場合、上記の値がデフォルトとなります。

基本的な使い方

ノードの追加・編集・削除

モデルのsaveメソッドを呼び出したときに自動でlft,rghtに値がセットされます。
モデルのdeleteメソッドを呼び出したときには、子ノードも含め全て削除されます。
ただし、必ずparent_idを指定して下さい。(ルートノードのparent_idには0もしくはNULLを指定)
なお、ルートノードは1つだけ作成できます

メソッドの呼び出し方法

controller等から

TreeOrm\Tree::forge('モデル名')->メソッド名();

または、

$data = モデル::find($id);
TreeOrm\Tree::forge($data)->メソッド名();

メソッド一覧

change( $id1 = null, $id2 = null)

ノードの入れ替え
$id1 対象ノード1のキー
$id2 対象ノード2のキー
返り値 成功したかどうか

childCount( $id = null, $direct = false)

子ノード数の取得をします
$id キー・falseを指定した場合ルートノード、nullを指定した場合インスタンスのノードを示します
$direct 直下の子のみを対象とするか。直下の子のみならばTRUE、子要素全て取得ならばFALSE
元のノードが存在していれば子ノード数、存在していなければfalseを返す

children( $id = null, $direct = false, $orderColumn = null, $orderDirection = null, $limit = null)

子ノードの取得をします
$id キー・falseを指定した場合ルートノード、nullを指定した場合インスタンスのノードを示します
$direct 直下の子のみを対象とするか。直下の子のみならばTRUE、子要素全て取得ならばFALSE
$orderColumn ソートカラム名
$orderDirection ソート順の指定、'DESC' または 'ASC'。デフォルトは'ASC'。
$limit 取得最大数
元のノードが存在していれば子ノードの配列、存在していなければfalseを返します

getParentNode( $id = null)

親ノードの取得をします
$id キー・nullを指定した場合インスタンスのノードを示します
親ノードが存在していれば親ノードの配列、存在していなければfalseを返します

getPath( $id = null)

パスの取得をします
$id キー・nullを指定した場合インスタンスのノードを示します
ノードが存在していればルートから順番に配列として、存在していなければfalseを返します

moveDown( $id = null, $number = 1)

ノードの階層を変えずに位置を下げる
$id キー・nullを指定した場合インスタンスのノードを示します
$number 移動回数、TRUEを指定した場合、端まで移動

moveUp( $id = null, $number = 1)

ノードの階層を変えずに位置を上げる
$id キー・nullを指定した場合インスタンスのノードを示します
$number 移動回数、TRUEを指定した場合、端まで移動

reorder( $id = null, $field = null, $order = 'ASC')

ノードの再配置を行います。lft、rghtが未設定の場合はparent_idに基づき自動的に設定します。
初期データを登録するときなどに使用して下さい。
$id キー・falseを指定した場合ルートノード、nullを指定した場合インスタンスのノードを示します
$orderColumn ソートカラム名
$orderDirection ソート順の指定、'DESC' または 'ASC'。デフォルトは'ASC'。

reset()

ノードの添字振りなおしを行います。
ノードの削除などで添え字が連続しなくなったときに使用して下さい。

注意事項

モデル名でメソッドの呼び出しを行った場合、現在位置は一番最初に見つかったノード(通常はidが一番若いノード、ほぼルートノード)となります

今後の予定・改良点など

まずテストケースを作らなければならない…
しかしながら、どうすればいいか検討がつきません…

FuelPHPでもTree Behavior

FuelPHP

CakePHPにはTree BehaviorとTree Helperといった便利なものがあります。
詳しくは、
CakePHPの「OrderedBehavior」と「TreeBehavior」はマジで使うべき
とかを参照してもらうとして、
簡単にツリー構造を使いたい!といった要求から
こんなものを作ってしまいました。

FuelPHP TreeOrm Package

しかしながら、未だ要素の追加、削除、親IDの変更しか作っていませんが…

使い方

設置

次の位置においてください

FuelPHP TreeOrm Package

/fuel/packages/treeorm

config.phpを編集

treeormとormをAPPPATH/config/config.php の always_load の部分に追加します

return array(
    'always_load' => array(
        'packages' => array(
            'orm', // 追加
            'treeorm', // 追加
        ),
    )
);

該当のモデルにovserversやフィールド等の追加

use Orm\Model;

class Model_Testtree extends Model
{
    protected static $_properties = array(
		'lft',
		'rght',
		'parent_id',
	);

	protected static $_observers = array(
		'TreeOrm\\Observer_Tree' => array(
			'events' => array('before_save','before_delete'),
		),
	);
}

こいつを使うには必要なフィールドがあります。

`id` int unsigned NOT NULL auto_increment,
`parent_id` int unsigned default '0',
`lft` int(10) default '0',
`rght` int(10) default '0',
PRIMARY KEY (`id`)

これで、とりあえず使えるようになるはず

ソースを見ていただければわかるのだが、非常に汚いし、機能も全然ないのでそのうち綺麗に作り込む予定

FuelPHP Plupload Packageを作った

FuelPHP Plupload jQuery jQuery UI

巨大ファイルや複数ファイルのアップロードにPluploadが非常に使えるのでFuelPHPのパッケージを作ってみた。

必要なもの

設置方法

次の位置においてください

FuelPHP Plupload Package

/fuel/packages/plupload

Plupload

jsフォルダを/public/assets/plupload

jQueryjQuery UIについては、適当にCDNなりローカルからの読み込み等を行って下さい

実装例

/fuel/app/classes/controller/plupload.php

<?php
class Controller_Plupload extends Controller
{
    function action_index()
	{
		return View::forge('plupload');
	}

	static function upload_callback($filename)
	{
    	// アップロード終了時処理
    	// $filenameに保存されたファイル名を返す
	}
	
	function action_upload()
	{
		Plupload::upload('Controller_Plupload::upload_callback');
	}
}

/fuel/app/views/plupload.php

<!DOCTYPE HTML>
<html lang="ja-JP">
    <head>
		<meta charset="UTF-8">
		<title>Plupload</title>
        ここにjQueryjQuery UIの読み込み処理を挿入
		<?php echo Plupload::insert_header(); ?>
	</head>
	<body>
		<form method="post" action="examples_dump.php">
		<div id="uploader" style="height: 330px; width: 500px;">
			<p>You browser doesn't have Flash, Silverlight, Gears, BrowserPlus or HTML5 support.</p>
		</form>
	</body>
</html>

/fuel/app/config/plupload.php(/fuel/packages/plupload/config/plupload.phpからコピーして編集でもよい)

<?php
return array(
    'url' => \Uri::create('plupload/upload'),
	'ui' => 'jqueryui',
);

参考にした所

CakePHP Plupload Plugin | 管理人の日記~つらつらなるままに~

jQueryUI読み込みのフォールバック処理

jQuery jQuery UI

前回のjQueryのフォールバックを行ったので、今度はjQuery UIのフォールバックでも

<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.21/themes/smoothness/jquery-ui.css" type="text/css" />
<script type="text/javascript">
$.each(document.styleSheets, function(i,sheet){
 if(sheet.href){
  if(sheet.href.split(':')[1]=='//ajax.googleapis.com/ajax/libs/jqueryui/1.8.21/themes/smoothness/jquery-ui.css') {
   var tID1 = setInterval(function(){
    if(sheet.rules || sheet.cssRules){
     clearInterval(tID1);
     var rules = sheet.rules ? sheet.rules : sheet.cssRules;
     if (rules.length == 0) {
      $('<link type="text/css" rel="stylesheet" href="/css/smoothness/jquery-ui-1.8.21.custom.css" />').appendTo('head');
     }
    }
   },10);
  }
 }
})
</script>
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.21/jquery-ui.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.21/i18n/jquery-ui-i18n.min.js"></script>
<script>window.jQuery.ui || document.write('<script src="/js/jquery-ui-1.8.21.custom.min.js"><\/script>')</script>

jQueryとは違い、CSSの処理が増えているのが難しい所

rulesかcssRulesがセットされるまでsetIntervalでループさせ、セットされたら長さのチェック、長さ0ならローカルのCSSを読み込むと行った流れ。