2012年10月31日水曜日

Notice: Undefined index が消えない・・・なんで?

CakePHPをさわってて最初のエラー。
題名のとおり「Notice: Underfined index」が。。。


Windows+Apache+MySQLでは動いてるんですが、Mac+Apache+MySQLで上のような画面に。。。

やってることをデータベースに以下のテーブルを追加
CREATE  TABLE `cakephp`.`posts` (
  `id` INT NOT NULL AUTO_INCREMENT ,
  `title` VARCHAR(45) NOT NULL ,
  `description` TEXT NOT NULL ,
  `created` DATETIME NOT NULL ,
  `modified` DATETIME NOT NULL ,
  PRIMARY KEY (`id`) );

コントローラ(app/Controller/PostsController.php)
<?php
class PostsController extends AppController
{
    public $scaffold;
}
やってることはこれだけなんです。
登録はできてるのですが一覧がでないという現象。
何かの設定だとは思うんですが。。。

試しにNoticeなのでphp.iniで以下の設定を
error_reporting=E_ALL & ~E_NOTICE

変わらず。。。??

CakePHP側で制御やってるのかなと思ってConfigを見てたらそれっぽいのが。
    Configure::write('Error', array(
        'handler' => 'ErrorHandler::handleError',
        'level' => E_ALL & ~E_NOTICE,    // E_ALL & ~E_DEPRECATED
        'trace' => true
));

これでエラーは消えましたが何もでてきません。


何方か詳しいかた教えて下さい。

※途中経過
scaffoldではなくコントローラ・モデル・ビューを作って試してみました。
けど結果は同じ。エラーがでたので取得したデータを見てみると
Array
(
    [0] => Array
        (
            [0] => Array // ←ココがヘン!?
                (
                    [id] => 1
                    [title] => sample
                    [description] => sample description
                    [created] => 2012-10-31 11:32:46
                    [modified] => 2012-10-31 11:32:46
                )

        )

)

通常の動きであれば
Array
(
    [0] => Array
        (
            [Post] => Array // 0 ではなく Post
                (
                    [id] => 1
                    [title] => sample
                    [description] => sample description
                    [created] => 2012-10-31 11:32:46
                    [modified] => 2012-10-31 11:32:46
                )

        )

)

だと思うんですが。。。上の結果から
foreach ($posts as $post) {
    echo $post[0]['id'];      // OK
    echo $post['Post']['id']; // NG
}
なんでこんなことになるんだろうな。


※やっとこさ解決!!!
やっぱり環境まわりでした。
CentOSに一から環境を作りなおしたら同じ現象を再現できたのでいろいろ調べた結果。。。
ほんと凡ミスというか設定足らずなのかな。
PDOのインストールとかは特に問題なかったんですがpdo_mysqlのあと、phpのconfigureを再度やり直しただけ。
./configure \
 --with-pdo-mysql=/usr/local/mysql \
 ...

これを追記しただけ。
お騒がせしました。

※追記。。。
configureから再度やり直さなくてもphp.iniにMySQLのsocketを追記するだけでもOKでした。
pdo_mysql.default_socket=/tmp/mysql.socket



2012年10月29日月曜日

Contoller_Hybridも少し試してました #fuelphp

これもメモ程度に。
FuelPHPのコントローラには、Controller、Controller_Template、Controller_Rest、Controller_Hybridの4種類があります。

Controllerはベースとなるクラスであとの3つはこれを拡張したクラスになってます。
今回少し試してみたのはController_Hybrid。
このController_Hybridは、Controller_TemplateとController_Restの両方を組み合わせたようなクラスになってます。

なんかすごそうだなと思ってコアのほうのコード(core/controller/hybrid.php)を見てみると、このクラスは Controller_Rest を継承しています。
RestコントローラとTemplateコントローラの処理をわけるのに書いてることは「 is_ajax()」。(他にも少し処理はありますが)

ですのでHybridコントローラを用いてコントローラを構築した場合、単純にURLにアクセスしてjsonとかを吐き出すことはできないみたいです。
それでちょっと試してことを簡単に。

app/classes/controller/hyb.php
<?php
class Controller_Hyb extends Controller_Hybrid
{
    public function action_index()
    {
        $this->template->title = 'Hybrid_Template';
        $this->template->content = View::forge('hyb/index');
    }

    public function get_list()
    {
        $data = array(
            'method' => 'GET',
            'foo' => Input::get('foo')
        );
    }

    public function post_list()
    {
        $data = array(
            'method' => 'POST',
            'foo' => Input::post('foo')
        );
    }
}

view/hyb/index.php
<p>Hybrid</p>

<input type="text" name="val" id="form_val" value="bar" />
<button id="btn_g">GET</button>
<button id="btn_p">POST</button>

<?php echo Asset::js('jquery-1.8.2.min.js'); ?>
<script type="text/javascript">
    $(document).ready(function() {
        $('#btn_g').click(function() {
            $.get('<?php echo Uri::create('hyb/list.php'); ?>', 
                    { bar: $('#form_val').val() },
                    function(str) {
                        alert(str);
                    });
        });
        $('#btn_p').click(function() {
            $.post('<?php echo Uri::create('hyb/list.php'); ?>', 
                    { bar: $('#form_val').val() },
                    function(str) {
                        alert(str);
                    });
        });
    });
</script>

このコードのURLを例えばhttp://localhost/fuelphp/public/index.php/hyb/としてアクセスするとまずは action_index が呼び出され、入力ボックスとボタンが2つ表示されると思います。
ボタンは GET と POST があり、GET ボタンをクリックすると get_listが POSTボタンをクリックすると post_list が呼び出されているのがわかると思います。

ここは問題なく動作すると思いますが、最初に少し書きましたが「is_ajax()」の判断でRESTコントローラを呼び出してるので
http://localhost/fuelphp/public/index.php/hyb/list.json?bar=bar
というURLに直接アクセスしてもエラーになります。


ちなみに。。。
「is_ajax()」でRESTコントローラを呼び出してるのでコントローラ側に「action_create」とかいうメソッドを作ってajaxで呼び出してみました。
が、予想通り何も反応してくれませんでした。

Hybridコントローラを使ってajax以外からもRESTFulな出力ができればいいのにと感じた次第です。
できるのかな?できるのであれば誰か教えてください!!



2012年10月27日土曜日

oil generate を少し試してみた #fuelphp

前に scaffold について書きましたが、もう少し試してみたいなと思って oil generate のドキュメント見ながらマイグレーションファイルについて調べてみました。
メモ程度に。

モデルとマイグレーションを一緒に作る
$ php oil g model article title:varchar[45] description:text
    Creating model: c:\workspace\fp-sample\fuel\app\classes\model\article.php
    Creating migration: c:\workspace\fp-sample\fuel\app\migrations\001_create_articles.php

生成されたマイグレーション
<?php

namespace Fuel\Migrations;

class Create_articles
{
    public function up()
    {
        \DBUtil::create_table('articles', array(
            'id' => array('constraint' => 11, 'type' => 'int', 'auto_increment' => true),
            'title' => array('constraint' => 45, 'type' => 'varchar'),
            'description' => array('type' => 'text'),
            'created_at' => array('constraint' => 11, 'type' => 'int'),
            'updated_at' => array('constraint' => 11, 'type' => 'int'),

        ), array('id'));
    }

    public function down()
    {
        \DBUtil::drop_table('articles');
    }
}


これを実行する場合は
$ php oil refine migrate

これでOK。データベースを確認すると articles というテーブルが作成されていると思います。
ちなみに生成されたマイグレーションファイルには up() と down() とがありますが、意味はそのままで up() は新しく変更される内容、down() は取り消し内容といったかんじになります。
一度実行したマイグレーションを取り消す場合にはこの down() を実行すればOK。
$ php oil refine migrate:down

次に oil コマンドを入力したときは title と description しか宣言してないですが id, created_at, updated_at が自動的に生成されています。
id の消し方はわからなかったんですが(というかあるのかな??)、created_at と updated_at を消したい場合は --no-timestamp を付加してやります。
$ php oil g model article title:varchar[45] description:text --no-timestamp

created_at とか updated_at の名称を変更したい場合は
$ php oil g model article title:varchar[45] description:text --created-at=my_created --updated-at=my_updated

マイグレーションはいらないって場合は --no-migration とするとOK。
$ php oil g model article title:varchar[45] description:text --no-migration


つぎに、マイグレーションだけが作りたいという場合は oil g migration で生成できるんですがこれは規定のプリフィックスにしたがってコマンドを入力します。

新規テーブル(postsテーブルを生成)
$ php oil g migration create_post title:varchar[45] description:text

テーブルのリネーム(postsテーブルをpagesにリネーム)
$ php oil g migration rename_table_posts_to_pages

フィールドの追加(pagesテーブルにユーザ情報:名前、年齢、性別を追加)
$ php oil g migration add_userinfo_to_pages name:varchar[45] age:int gender:varchar[10]

フィールド名の変更(pagesテーブルのnameをusernameに変更)
$ php oil g migration rename_field_name_to_username_in_pages

テーブルの削除(pagesテーブルの削除)
$ php oil g migration drop_pages


といったかんじ。
いろいろ書きましたが書いてることはドキュメントに載ってることなんで。
詳しくはやっぱりドキュメントを見たほうが役に立ちますね。


Generate - Oil Package - FuelPHP Documentation
http://docs.fuelphp.com/packages/oil/generate.html



2012年10月21日日曜日

scaffoldで生成されたコードでvalidationを出すと... #fuelphp

FuelPHPでアプリケーションを作成する場合 oil generate の scaffolding というのを使うと一発で一覧、作成、編集画面を作ってくれます。
$ php oil g scaffold article title:nvarchar[45] body:text

上のようなコマンドを実行するだけでコントローラ、モデル、ビュー、マイグレーションを作成してくれます。
便利だなと思ってさわってみたら作成画面とかで validation エラーを出すと下のようなエラーが表示されてしまいました。
Fatal error: Uncaught exception 'Fuel\Core\FuelException' with message 'The session data stored by the application in the cookie exceeds 4Kb. Select a different session storage driver.

クッキーの容量オーバー??4KB??

scaffoldで生成されたコントローラを見ると
$val = Model_Article::validate('create');
if ($val->run())
{
    (省略)
}
else
{
    Session::set_flash('error', $val->error());
}

validation で例外が発生した場合、$val->error() をセッションに格納しています。
ドキュメントを見てみると「バリデーションエラーをフィールドとエラー内容の組の配列で取得する」と書かれてましたが実際に出力されている内容がわからなかったのでセッションのドライバをファイルにしてみました。

変更のやり方は下のサイトが参考になります。
Fuelphp、セッションをファイルやDBに切り替える

修正箇所は2箇所。
core/config/session.php を app/config/session.php にコピーして
return array(
    'driver' => 'file',

    (省略)

    'file' => array(
        'cookie_name'     => 'fuelfid',
        'path'            => APPPATH.'/tmp',
        'gc_probability'  => 5
    ),

    (省略)
);

これで validation エラーを再度発生させてみると、エラー表示はでなくなりました。
実際に出力されたファイルを見てみるとHTMLのソースとか何とかがでてきました。ファイルサイズを見てみると7KB。。。
確かに4KB超えてる。。。

実際のプロジェクトとかで scaffold で生成されたコードをそのまま使うということはほとんどないと思いますが、セッションのドライバを変えるか。。。もしくは以下のようなかんじでエラー内容だけを出力するように修正。
$val = Model_Article::validate('create');
if ($val->run())
{
    (省略)
}
else
{
//    Session::set_flash('error', $val->error());
    Session::set_flash('error', $val->show_errors());
}


これでOK。

oil generate は scaffolding みたいに一気にすべてを生成するだけじゃなくてコントローラとかモデルとかそれぞれで生成することができるので便利は便利です。

詳しくはドキュメントを
Genarate - Oil Package - FuelPHP Documentation




2012年10月16日火曜日

以前につくった認証パッケージをGithubに載せました

SimpleAuthについて少し前にこんな記事を書いてました。
http://teru2-bo2.blogspot.jp/2012/07/simpleauthfuelphp.html


このときはコード公開してませんでしたが、とりあえずGithubに載せたんで。
(READMEもいずれ書く・・・かな)
もともとのSimpleAuthを自分に合うように修正していっただけですけどね。

EasyAuth
https://github.com/hayashida/EasyAuth

git cloneしてもらってあとは配置するだけ。場所はどこでもいいんですけどそのままプロジェクトに上書きすると「fuel/app」に配置されます。

あとは「fuel/app/bootstrap.php」に以下を追記
'Auth\\Auth_Login_EasyAuth' => APPPATH . '/classes/Auth/Login/easyauth.php',


これで動くと思います。
少し手を加えただけなんでこうしたほうがいいとか、こうして欲しいとかなんかあれば教えてください。


FuelPHPの認証パッケージのまとめはこちらにも載ってます。
http://fuelphp.phpfogapp.com/wiki/index/FuelPHP%20%E3%81%AE%E8%AA%8D%E8%A8%BC%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8



2012年10月11日木曜日

オートローダについて(FuelPHP)

久しぶりにFuelPHPネタ。
といっても対した内容ではないですが。

FuelPHPではオートローダによって自作したクラスとかも自動的にロードされるようになっています。
ただし、これには規約があって
・ファイル名とフォルダ名はすべて小文字
・クラス名の中のアンダースコア(_)はフォルダ区切り
というのがあります。

たとえば「fuel/app/classes」に以下のようなディレクトリ構成でfoo.phpを作成した場合
classes
└ libs
  └ foo.php

<?php
class Libs_Foo
{
    function hoge()
    {
    }
}

こんなかんじで実際に使用する場合は
<?php
$foo = new Libs_Foo();
$foo->hoge();

これでOK。

フレームワークを使っていない場合でも
<?php
define('MY_CLASS_PATH', 'classes/');

// autoload
function __autoload($className) {
    $className = ltrim($className, '\\');
     $fileName = '';
     $namespace = '';
    if ($lastNsPos = strripos($className, '\\')) {
        $namespace = substr($className, 0, $lastNsPos);
        $className = substr($className, $lastNsPos + 1);
        $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace);
    }
    $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';

    require MY_CLASS_PATH . $fileName;
}

PSR-0のページに載ってある例をそのままですが
こんなかんじでオートローダを実装しておけば同じようなことができます。

また、SplClassLoaderというのがあって
https://gist.github.com/221634
これが推奨されているものみたいです。



オートローダを使った場合、requireなのかinlucdeなのか、require_onceなのかinlucde_onceなのか。。。
どっちが一般的なんだろう。。。
FuelPHPはrequireみたいなんだけどrequireする前にfile_existsをしているからそれでもいいのかな。



2012年10月10日水曜日

twitterbootstrapを久しぶりに


最近いろいろとツールとかなんとかでてきてるので。

むかーーし、さわったまま今まで何もしてなかったのでもう一回思い出しながらさわってみました。
今回は手軽に「jsdo.it」を使ってみました。
「jsdo.it」はWeb上でHTML5、CSS、Javascriptを書いてその場で実行できるというWebサービス。
環境つくるのが面倒だとかちょっとだけ試したいだけとかけっこう手軽に使えます。

ほかの人の書いたコードとか動きとかも見れるので見るだけでもけっこうおもしろいです。


えー本題ですが「TwitterBootstrap」
本当はjQuery UIとかと比較してみたかったんですがとりあえずTwitterBootstrapの基本的な実装だけ。

Menu


Tab


Table


jQuery UIに比べるとかなり手軽に使える印象です。
まだCSSのカスタマイズとかどこまで自由にできるかわかりませんがいいです。

ちなみにTwitterBootstrapをとりあえず試したいって人は「jetstrap」というサービスもけっこうおもしろいです。

jetstrap



2012年10月5日金曜日

CSS3でバリデーションエラーをスタイルする

すべては下のページです。
[Bookmark]JavaScriptなしでレスポンシブでバリデーション機能付きのフォームを実装するHTML5チュートリアル :: Creatorish


どうなってるんだろうと思ってソースを見ましたが。。。
CSS3で input の疑似クラス :invalid と :valid 。
これだけでした。
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>HTML5 forms</title>
    <style>
        input:invalid {
            background: #ffcccc;
        }
        input:valid {
            background: #fff;
        }
    </style>
</head>

<body>
    <header><h1>HTML5 forms</h1></header>
    <form>
        <label for="form_name">Name</label>
        <input type="text" name="name" id="form_name" placeholder="your name" required>

        <label for="form_email">E-Mail</label>
        <input type="email" name="email" id="form_email" placeholder="foo@example.com" required>

        <input type="submit" value="SEND">
    </form>
</body>

</html>


知りませんでした。。。



HTML5でオフラインWEBアプリを作る

HTML5の「アプリケーションキャッシュ」機能と「ウェブストレージ」機能を使うことで簡単にオフラインのWEBアプリを作ることができます。

ここではToDoアプリを例にざっくり説明。

アプリケーションキャッシュ(Application Cahche)
オフラインでも動作するWEBアプリを作成するには、キャッシュしたいファイルを「マニフェスト」というファイルに列挙するだけです。

todo.appcache
CACHE MANIFEST
# Version 1.0

CACHE:
# キャッシュするファイル
index.htm
js/todo.js

NETWORK:
# キャッシュしないファイル

このマニフェストファイルは「CACHE」と「NETWORK」という2つのセクションで構成されていて、コメントにも書いてますがそれぞれキャッシュするファイル、キャッシュしないファイルを記述していきます。

また、このマニフェストファイルですがMIMEタイプが「text/cache-minifest」として出力される必要がありますので使っているWEBサーバに合わせてMIMEタイプを追加して下さい。

Apacheサーバ+.htaccess
AddType text/cache-manifest .appcache


HTML
HTMLで注意する点は2つ。
まずHTML5なので<!DOCTYPE html>を宣言すること。
もう1つは上で作成したマニフェストファイルをhtmlタグのmanifest属性で指定すること。
これくらいです。
index.htm
<!DOCTYPE HTML>
<html lang="ja-JP" manifest="todo.appcache">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width" />
    <title>オフラインTODO</title>
    <script type="text/javascript" src="./js/todo.js"></script>
</head>
<body>
    <div>
        TODO:
        <input type="text" id="newItem" style="width:50%;" />
        <button onclick="addTodo();">追加</button>
    </div>
    <p id="todo"></p>
</body>
</html>

マニフェストファイルの参照がうまくいってればこれだけでオフラインのWEBアプリの完成です。(JSがまだないから動きはしませんが)
スマートフォンとかで試す場合は、機内モードとかにすると確認することができます。
ネットワークが繋がってない状態でも更新!としてちゃんとページが表示されます。


Javascript
Javascriptではウェブストレージ機能を使ってローカルストレージに内容を保存する機能を実装していきます。
また、合わせてキャッシュの更新を通知するような仕組みも実装します。
js/todo.js
var pre = "TodoApp.";    // WebStorageのプリフィックス
var ls = window.localStorage;

// Todoを追加する
function addTodo() {
    var i = 0;
    if (ls[pre + "count"]) i = parseInt(ls[pre + "count"]);

    ls[pre + "item" + i] = $("newItem").value;
    $("newItem").value = "";

    ls[pre + "count"] = (i + 1);
    updateList();
}

// Todoを削除する
function rmTodo(no) {
    ls.removeItem(pre + "item" + no);
    updateList();
}

// Todo一覧を更新する
function updateList() {
    var src = "";
    var count = 0;
    if (ls[pre + "count"]) count = parseInt(ls[pre + "count"]);

    for (var i = 0; i < count; i++) {
        if (ls[pre + "item" + i]) {
            var btn = "<button onclick='rmTodo(" + i + ");'>×</button>";
            src += "<div>" + btn + ls[pre + "item" + i] + "</div>";
        }
    }

    $("todo").innerHTML = src;
}

function $(id) {
    return document.getElementById(id);
}

// ロード時
window.onload = function() {
    var appCache = window.applicationCache;
    
    // マニフェストの更新を通知する
    appCache.addEventListener("updateready", function() {
        if (confirm("最新版に更新しますか?")) {
            appCache.swapCache();
            location.reload();
        }
    });

    updateList();
}

まず、ローカルストレージの使い方ですが簡単です。
// 値を書き込む場合
localStorage["key"] = "value";

// 値を取得する場合
var value = localStorage["key"];

// 値を削除する場合
localStorage.remoteItem("key");

クッキーに似てますがローカルストレージは永続的にデータが保持されるのでけっこう使えます。

つぎにマニフェストの更新の通知です。
アプリケーションキャッシュを操作するAPIを利用するには、window.applicationCacheオブジェクトを利用します。
このオブジェクトのイベントリスナーに「updateready」を登録することで、キャッシュの更新が可能なタイミングでアラートを表示することができます。
そこで、swapCache()メソッドを呼び出すことでキャッシュを更新することができます。


あいかわらずざっくりした説明ですが、これでオフラインWEBアプリの完成です。





2012年10月3日水曜日

jQueryで郵便番号検索API 其ノ二

少し前にくじら郵便番号APIというのを紹介してました。
jQueryで郵便番号APIを使う

これで申し分ないんですがひとつできないことがありました。
それはSSLページへの対応。

くじら郵便番号APIのアドレスはhttpから始まりますのでhttpsのページからこのAPIを呼びだそうとしたとき警告が出て?うまいこと動作しませんでした。

そこでまた何かないかなって探してたらいいのがありました。
ajaxzip3 - 郵便番号検索API

これはAPIを提供というよりはスクリプトを利用するかんじ。
ページを見ると使い方も書かれてるのでわかりやすいと思いますがここでも少し紹介。
<html>

<head>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
    <title>サンプル - 郵便番号検索API</title>
    <script type="text/javascript" src="http://ajaxzip3.googlecode.com/svn/trunk/ajaxzip3/ajaxzip3.js" charset="utf-8"></script>
</head>

<body>
    郵便番号:
    <input type="text" name="zipcode_1" maxlength="3" size="3" />
    -
    <input type="text" name="zipcode_2" maxlength="4" size="4" onkeyup="AjaxZip3.zip2addr('zipcode_1','zipcode_2','pref','address');" />
    <hr />
    都道府県:
    <input type="text" name="pref" size="10" />
    住所:
    <input type="text" name="address" size="50" />
</body>

</html>

この例では二番目の郵便番号を入力した際に自動的に郵便番号を検索し、都道府県名および住所を取得・表示するような動きになります。

この関数の引数は
AjaxZip3.zip2addr( '〒上3桁' , '〒下4桁' , '都道府県' , '市区町村', '町域大字' , '丁目番地' )


上のサンプルや本家のページサンプルでは郵便番号検索のトリガーを onkeyup としていますがもちろんボタンを別に用意しといて任意のタイミングで検索することもできます。
<script type="text/javascript">
    $(document).ready(function() {
        $("#btn").click(function() {
            AjaxZip3.zip2addr('zipcode_1', 'zipcode_2', 'pref', 'address');
        });
    });
</script>

<input type="text" name="zipcode_1" maxlength="3" size="3" />
-
<input type="text" name="zipcode_2" maxlength="4" size="4" />
<input type="button" id="btn" value="検索" />


簡単!というかそのままですね。
しかし、httpsにも対応してるしjsonpに対応してるのでどこいっても使えるAPIかなと思います。