コンソールで自作shellを実行するとパーミッションエラーになる

結構はまりました。原因がわかるまで意味がわからず時間をくってしまったので書いておきます。

症状

こんな感じのエラーがだーーーっとでまくり。

<?php
SplFileInfo::openFile(/app/tmp/cache/persistent/myapp_core_cake_console_):failed to open stream:Permission denied in /lib//FileEngine.php line 293
?>

で、/app/tmp/cache/modelの中身を削除して実行すると、今度はブラウザ側で同じエラーが表示される。

原因

/app/tmp/cache/model/app/tmp/cache/persistent内のキャッシュファイルのパーミッションが644になっている。
自分の環境だとブラウザからのアクションで生成されるキャッシュファイルは所有権がnobodyになっていて、コンソールからCakeシェルを実行するときは当然のようにhoge(ユーザー名)になってるわけです。

対策

/app/Congfig/core.phpmaskパラメーターを追加する。これでキャッシュファイルの生成時のパーミッションを設定することができます。

/app/Congfig/core.php
<?php
/**
 * Configure the cache used for general framework caching.  Path information,
 * object listings, and translation cache files are stored with this configuration.
 */
Cache::config('_cake_core_', array(
    'engine' => $engine,
    'prefix' => $prefix . 'cake_core_',
    'path' => CACHE . 'persistent' . DS,
    'serialize' => ($engine === 'File'),
    'duration' => $duration,
    'mask' => 0666
));

/**
 * Configure the cache for model and datasource caches.  This cache configuration
 * is used to store schema descriptions, and table listings in connections.
 */
Cache::config('_cake_model_', array(
    'engine' => $engine,
    'prefix' => $prefix . 'cake_model_',
    'path' => CACHE . 'models' . DS,
    'serialize' => ($engine === 'File'),
    'duration' => $duration,
    'mask' => 0666
));
?>

参考にしたサイト

  • stackoverflow

You can resolve this by adding a mask to your config in core.php

Cache::config('default', array(
    'engine' => 'File',
    'mask' => 0666,
));

cakephp - SplFileInfo::openFile(/app/tmp/cache/persistent/cake_core_cake_console_):failed to open stream:Permission denied in /lib/.../FileEngine.php line 293 - Stack Overflow

最近このサイトよく見ますね。数値です、文字列の"0666"だとおかしくなっちゃいます。実はこれでハマった。

  • Cake Book

When using the FileEngine you might need to use the mask option to ensure cache files are made with the correct permissions.
Caching — CakePHP Cookbook v2.x documentation

マニュアルにもちょっとだけ書いてある。

外出先から(ルーター越しに)iTunesの曲を聞く

Apple TVがあるやん、とかお手頃なNASが最近出てきたよ、とか、規約的にグレーだよ、とか、はい、そうなんです。でも一家に一台くらいMacBook余ってるっしょ?どうやって有効に使いますか?という話題。我が家は1台余ってるMacBookiTunesの再生とファイルサーバー専用に使っています。


が、HDL2-Aシリーズ | ネットワークハードディスク | IODATA アイ・オー・データ機器これ出てきてこの記事書く気無くしてしもた。


ほとんどは以下を参考にしてもらえればOK。
SSHは標準で「リモートログイン」という機能がある。

これだけでOK。だけど、証明書発行したりしてパスワード認証なしにするとセキュアだよって話はhttp://goo.gl/MYaDAで。
SSHなしでVPNだけでは動かないのでご注意を。なんでそんなしたかというと、私ってこの仕組で動作する経路をまったく理解してなかったわ〜。あっはっは〜。

(6) DAAP over SSH Port Forwarding (iTunes music共有)
上記プリンタ共有と同様に「Network Beacon」を使用する。iTunes music共有を有効にしているPCのアドレスを事前に知る必要がある。また、ここで述べる方法では、LAN内の複数の共有先を認識することはできない。LAN内で複数の共有先があったとしても、どれか1台にのみアクセスできる。主に自分のオフィスのPCを共有することを想定している。

「Network Beacon」を使ってlocalhostをproxy hostにする 。
(Network Beaconの設定)
Service Name: (任意)(ここでは"daap_on_SSH"とする)
Service Type: _daap._tcp.
Port Number: 3689
Text Record: (Empty)
Enable Host Proxy: (Checked)
Host Name: localhost
IP Address: 127.0.0.1

サーバポート=3689(daap)
ローカルポート=3689
(ターミナル)ssh –L 3689:server_address:3689 user@sshd_address
iTunes) 共有している音楽の検索が有効になっていれば、自動的に”daap_on_SSH”の名前で認識される。音楽リストを読み込むと共有先の名前も本来のものに変更される。
※上記の方法ではあくまで特定の一つの共有先にしかアクセスできない。しかも相手のIPアドレスを知らなければforwardingできない。これに対して、例えば、RNSplicerなどを使うと、ローカルエリア内の全ての共有先にアクセスできるようになる。

#7 SSH Port Forwardingあれこれ(2)Mac編 - 誰かさんのMac Life

補足すると、
server_address: 家においてきたMacのアドレス(通常だとグローバルIP)
sshd_address: 通常だとルーターでポートフォワーディングなんかしてるんだろうから、server_addressと同じ。
Network Beaconの設定
ダウンロードはこちらから

Network Beaconはクライアント側(外出先の端末)ですよ。

しかしこのサイト良く出来てるわー。遠隔でやりたいことここ見りゃほとんど出来るもの。
#7 SSH Port Forwardingあれこれ(2)Mac編 - 誰かさんのMac Life

AndroidiPhoneでこの方法を試す

いや、そもそも無理なんでした。これって、通常同一ネットワーク内でしかアクセスできないBonjourNetwork Beacon使ってアクセス可能にする方法。単純にSSHポートフォワーディングはAndroidでもできるけど、Bonjourネットワークに接続できるわけないわな。

iPhoneVPNで接続できたって記事を見かけたけど持ってないので詳しく調べてないのでわかりません。

AndroidでSHHならConnectBotってのがおすすめ。上記の記事にも書いてあるセキュアなVPNとかするのにも重宝する。

FormヘルパーのdateFormat

マニュアルシンプルに書き過ぎやーん!!!404 Not Found
view内に以下のようにする。

<?php echo $this->Form->input('field', array('dateFormat' => 'YMD')); ?>

ほんとにマニュアルはよく読みましょうと自分に何度も言ってあげたい。


















































CakePHP における型 フィールドのプロパティ
primary_key serial NOT NULL
string varchar(255)
text text
integer integer
float float
datetimetimestamp (Y-m-d H:i:s)
timestamp timestamp (Y-m-d H:i:s)
time time (H:i:s)
date date (Y-m-d)
binary bytea
boolean boolean
number numeric
inetinet

404 Not Found

postgreSQLの型がtimestamp without time zone の場合は時間が表示されました。以下のように$option['type']で回避ができますが、型はきっちり決めたほうがよいのではないかと。

<?php echo $this->Form->input('field', array('type' => 'date', 'dateFormat' => 'YMD')); ?>

このままだと月の表示は英語表記になる。単純に数字にしたい場合は以下のように設定する。
日本語対応のFormHelper::datetime() - 24時間CakePHPのような便利なヘルパーもあるみたい。

<?php
// cake -> view -> helper -> form.php
// 2129行目あたり、オリジナルをコメントアウトして以下のように変更した。
case 'month':
     if ($options['monthNames'] === true) {
        $data['01'] = __('01', true);
        $data['02'] = __('02', true);
        $data['03'] = __('03', true);
        $data['04'] = __('04', true);
        $data['05'] = __('05', true);
        $data['06'] = __('06', true);
        $data['07'] = __('07', true);
        $data['08'] = __('08', true);
        $data['09'] = __('09', true);
        $data['10'] = __('10', true);
        $data['11'] = __('11', true);
        $data['12'] = __('12', true);
        // $data['01'] = __('January', true);
        // $data['02'] = __('February', true);
        // $data['03'] = __('March', true);
        // $data['04'] = __('April', true);
        // $data['05'] = __('May', true);
        // $data['06'] = __('June', true);
        // $data['07'] = __('July', true);
        // $data['08'] = __('August', true);
        // $data['09'] = __('September', true);
        // $data['10'] = __('October', true);
        // $data['11'] = __('November', true);
        // $data['12'] = __('December', true);
     } else if (is_array($options['monthNames'])) {
          $data = $options['monthNames'];
     } else {
          for ($m = 1; $m <= 12; $m++) {
               $data[sprintf("%02s", $m)] = strftime("%m", mktime(1, 1, 1, $m, 1, 1999));
          }
     }
break;

trim()が原因で文字化けする

POSTデータをチェックする前に無駄な前後のスペースなどは取り除きたい場合。CakePHPで言うとbeforeValidateなんかで処理するケース。PHP: trim - Manualを使おうとすると文字化けする。

*1

<?php
// _は半角スペース_は全角スペース
$str = trim($str, '_\t\r\n\0\x0B_');

これは文字化けする。unicodeならいけるのかとUTF8で試すがダメみたい。なのでPHP: preg_replace - Manualで対応。UTF8でしか使えません。

<?php
// _は半角スペース_は全角スペース
$pattern = array('/^[_\t\r\n\0\x0B_]+/u', '/[_\t\r\n\0\x0B_]+$/u');
$str = preg_replace($pattern, '', $str);

*1:\円マークが表示されている場合はバックスラッシュと読みかえて下さい。

CakePHPはじめました

ことの始まり

これまでSmartyPEARPHPアプリを作ってきたのだけれど、独学なのでなんともMVCに自信が持てない。時間もかかってるので非効率な感じがずっとしていた。

小さいプログラムからプログラミングを始めて、データベースを扱うようになって、コードが多少複雑になり、見返すと自分のコードがスパゲティーなのにギョッとした。そこで、ふと目にしたMVCという考え方をなんとなく覚えて、できたアプリのソースがどんどん大きくなってきて、特にControllerがなぜか肥大化していることに、またギョッとする。
どうしようかと思いながらふっとフレームワークってなんだろうと思って始めてみたのがきっかけ。

なぜCakePHP?

Zend Framework SymfonyどうやらCakePHP以外で有名なのはこのあたり。CakePHPにした理由は2つ。

  1. 小規模な物を早く、簡単に作りたい。
  2. 記事が多そう。

現時点ではほんとに小規模でごく数人が使うだけのアプリを想定していること。PHPのことを検索してるとCakePHPの話題がよく目に付いたので、なんとなく参考になるものが多そうな気がした。

マニュアルって大事

結局データベース設計からやり直したけど、結果的には早く、バグの少ないものを作れている気がする。以下マニュアル読んで調べてみるまで分からなかったこと。

APPディレクトリ以外は変更を加えてはいけない。

コンソールからインストールした場合は不要ですが、まずapp_controllerとapp_modelをcoreディレクトリからコピーしてしまうこと。なんか問題が起きた時に複雑になってしまいました。

基本データの取得はFind

readとfind取得の方法があって混乱しました。以下のような記事を発見したのですが、

そう 実はおおもとのDBアクセスは findAll() なんです。。。
read にいたっては
read() にきたものをバラして find() に投げて
find() はそれをさらに解析して findall に投げるわけです。
いや だったら使い分けないで全部 findAll で細かく指定するよ。。。
ちなみに findcount() も findAll() してます。
findAllBy とか findBy 系はどうなんだろう。
他のとこで camelclass をばらしてるのでチョット動き違うかもしれません。

cakephp find findall read の違い ← Neo Inspiration

readを使うとfindの引数を細かく指定したりしないことが多く、ソースがすっきりするので、場合によって使い分けるとよいのかも。参照した記事がバージョンが古い記事なので、そういう意味でも公式のマニュアルの内容も確認しながら上記を参考にすると良いのかな、と。

今日はとりあえずここまで。

MDB2でデータを表示するのにページを分割して表示する

つまりは

前へ ...5 6 7 8 9 10 11... 次へ 6/24ページ目

みたいなものを表示したかったので作ってみた。上記であれば5または11をクリックするとナビゲーションの表示が切り替わる。「前へ」や「次へ」は単純に次のページなり前のページなりへ遷移。現在のページ情報はGETで送信しています。一部抜粋*1です。

まずは設定。

<?php
# conf.php
# ページナビゲーションの最大表示ページ数を設定

define('PAGE_LIMIT', 5);
?>

データベースの操作。
総数と1ページ分のデータを取得するのに2回もクエリを発行するのが、なんかこれでよいのか?と疑問。誰かつっこみください。

<?php
# model.php
# データベースへのアクセス
# データ取得時に該当データの総件数と1ページ分のデータを取得

require_once('MDB2.php');

// エラーハンドラ
function my_error() {
  print('DBアクセスエラーが発生しました.管理者にお問い合わせ下さい.');
  exit;
}

class model {
  protected $db;	// DBクラスオブジェクト
  
  // コンストラクタ
  function __construct() {
    $con =& MDB2::connect(DSN);	// DBへ接続

    if (PEAR::isError($con)) die($con->getMessage());
    $con->setErrorHandling(PEAR_ERROR_CALLBACK, 'var_dump');
    $this->db = $con;
  }
  // データ一覧の取得
  function get_datas($page, $limit) {
    $result = array('page' => $page);
    $res = $this->db->query('SELECT * FROM table ORDER BY update DESC');
    $result['numrows'] = $res->numRows(); 
    $this->db->setLimit($limit, ($page-1)*$limit);
    $res = $this->db->query('SELECT * FROM table ORDER BY update DESC');
    $result['rows'] = $res->fetchAll(MDB2_FETCHMODE_ASSOC);
    $res->free();
    return $result;
  }
  
  // データ一覧の取得(プリペアドステートメントを使用)
  function get_datas_by_id($id, $page, $limit) {
    $result = array('page' => $page);
    $types = array('text');
    $data = array('id' => $id);
    $sth = $this->db->prepare('SELECT * FROM table WHERE id = :id ORDER BY update DESC', $types);
    $res = $sth->execute($data);
    $result['numrows'] = $res->numRows();
    $this->db->setLimit($limit, ($page-1)*$limit);
    $sth = $this->db->prepare('SELECT * FROM postdata WHERE id = :id ORDER BY update DESC', $types);
    $res = $sth->execute($data);
    $result['rows'] = $res->fetchAll(MDB2_FETCHMODE_ASSOC);
    $sth->free();
    $res->free();
    return $result;
  }
}
?>

表示にはSmartyを使用しています。

<!--
  page_navi.tpl
  ページナビゲーション
-->
<div id="page_navi">
{if $navi.before}<a href="{$uri}&page={$navi.page-1}">前へ</a>{/if}
{if $navi.page_before}...<a href="{$uri}&page={$navi.block*$smarty.const.PAGE_LIMIT}">{$navi.block*$smarty.const.PAGE_LIMIT}</a>{/if}
{section name=i start=1 loop=$navi.loop+1}
  {if $navi.position === $smarty.section.i.index}
    {$navi.block*$smarty.const.PAGE_LIMIT+$smarty.section.i.index}
  {else}
    <a href="{$uri}&page={$navi.block*$smarty.const.PAGE_LIMIT+$smarty.section.i.index}">{$navi.block*$smarty.const.PAGE_LIMIT+$smarty.section.i.index}</a>
  {/if}
{/section}
{if $navi.page_next}<a href="{$uri}&page={$navi.block*$smarty.const.PAGE_LIMIT+6}">{$navi.block*$smarty.const.PAGE_LIMIT+6}</a>...{/if}
{if $navi.next}<a href="{$uri}&page={$navi.page+1}">次へ</a>{/if}
{$navi.page}/{$navi.num_page}ページ目
</div>

*1:コントローラー部分とSmartyへのアサイン処理とかは省略しました。