レガシーなシステムにCakePHPを使ってスマートフォンサイト作った時のメモ

zendで作られたWEBサイトのスマートフォン用を作ることになったのですが、zendでシステム化されているところは外注で作られた所らしく、
しかも暗号化されており、ソースコードも契約上もらえないという状況。
仕方がないので、既存の仕組みはそのままにしてCakePHPでスマートフォン版を作成する事にしました。
サーバー環境からPHP5.1なのでCakePHP1.3を使用しました。

目次

  • 基本
  • その他

基本

モデル

テーブルはCakePHPの命名規則には当てはまってないので、キーとテーブル名を指定して作成します。

class UserTbl extends AppModel {
    var $name = 'UserTbl';
    function __construct () {
        $id = array(
            'id'    => false,
            'table' => 'user_tbl',
        );

        parent::__construct($id);
        $this->primaryKey = 'user_id';
    }

}

また、複合キーを使ったテーブルもあるのでそこは諦めてSQL直書きで対応する事にしました。

class UserImageTbl extends AppModel {
    var $name = 'UserImageTbl';
    var $useTable = false; // 複合キーのため

    function findById($user_id, $image_id) {
        $sql = <<<SQL
SELECT
        *
    FROM
        user_image_tbl AS UserImageTbl
    WHERE
        user_image_user_id = ?
        AND
        user_image_id = ?
SQL;

        $userImage = $this->query($sql, array($user_id, $image_id));
        if (count($userImage) === 1) {
            return $userImage[0];
        }
        return $userImage;
    }
}

コントローラ

viewに渡すために$this->dataにモデルからとった値を設定します。

$profile = $this->UserProfileTbl->read(null, $id);
$this->data['ProfileBasic'] = $profile['UserProfileTbl'];

フォーム用モデル(バリデーションまで行う)とデータ登録用モデル(SQL発行)を別々に作成し、一つのフォームに一つのフォーム用モデル、
一つのテーブルに一つのデータ登録用モデル、という風に決めて作成しました。
取得後のキー名(テーブル名のキャメルケース)とフォームのキー名が違うので、フォームに渡す際や更新する際は都度設定する必要が有りましたが、その方がわかりやすいと思ったのでその様にしました。

ビュー

ヘルパーにモデル名とurlを指定して作成しました。

<?php echo $this->Form->create('Contact', array('url' => '/contact/', 'name' => 'contact-form'));?>

<?php echo $this->Form->text('Contact.mail', array('class' => 'w0')); ?>
<?php echo $this->Form->error('Contact.mail');?>

その他

ページング

複合キーの場合は、paginateも使用できないので自分で仕組みを作成する必要がありました。
ページングで必要な情報は、件数と表示するページの情報が最低限必要です。
件数はapp_model.phpに件数をFOUND_ROWS()を使って件数を返す関数を用意しました。

class AppModel extends Model {
    function found_rows () {
        $rows = $this->query('SELECT FOUND_ROWS();', false);
        if (!empty($rows)) {
            return $rows[0][0]['FOUND_ROWS()'];
        }
        return 0;
    }
}

あとは、検索用のSQLでSQL_CALC_FOUND_ROWSを指定すれば全件数は取得できます。

        $sql = <<<SQL
SELECT
        SQL_CALC_FOUND_ROWS
        *
    FROM
        {$tbl}
    WHERE
        {$where}
    ORDER BY {$order} DESC
    LIMIT    ?
    OFFSET   ?
SQL;

        $users = $this->query($sql, $param);
        $rows = $this->found_rows();
        $result = array();
        foreach ($users as $user) {
            $i = key(array_slice($user, 0, 1));
            $result[]['UserTbl'] = $user[$i];
        }
        return array($result, $rows);
    }

ページングのリンクは共通処理を行うコンポーネントを作成してリンクを生成するようにしました。

    /*----------------------------------------------------------
     検索結果共通処理(ページング)
     ----------------------------------------------------------*/
    function _setPagingLink ($query, $members, $all, $page, $type) {

        $this->set('query', $query);
        $this->set('members', $members);
        $this->set('all', $all);

        $maxpage = ceil($all / 10);

        // ページングリンク
        if ($page != 1) {
            $this->set('prev', '<a href="/s/search/'.$type.'/?page='.($page - 1).'&'.http_build_query($query).'">前へ</a>'."\n");
        }
        $start = ($page - 1) * 10;
        $end = $start + count($members);
        $this->set('counter', sprintf('%d件中/%d~%d件表示', $all, $start + 1, $end));
        if ($page != $maxpage && $maxpage != 0) {
            $this->set('next', '<a href="/s/search/'.$type.'/?page='.($page + 1).'&'.http_build_query($query).'">次へ</a>'."\n");
        }

        // セッション登録(検索結果戻る対策)
        $this->Session->delete('Search.back');
        $this->Session->write('Search.back.url', '/s/search/'.$type.'/?page='.($page).'&'.http_build_query($query));
        $this->Session->write('Search.back.pagename', '検索結果一覧');
    }

初めて使ったコンポーネント・プラグイン

SSL Component https://github.com/plank/secured
→メソッド単位でSSLの切り替えができるのでめっちゃ便利でした。(ちょっとだけパフォーマンス悪くなる!?)
Yalog: Yet Another Logger for CakePHP https://github.com/k1LoW/yalog
→Log4phpが簡単に使える様になります。オリジナルのログローテーションクラスもLog4phpには引けをとらないと思いました。

参考になったサイト

AutoLoginComponentを参考に自動ログイン実装しました。 http://d.hatena.ne.jp/sanojimaru/20091210/1260447577

Auth Componentを使用したかったのですが、ユーザー認証がAPI経由で行う仕様だったので非常に参考になりました。http://weble.org/2011/04/05/cakephp-oauth

portを開いているプロセスを特定する

portを開いているプロセスは、
lsof -i:[port]コマンドで確認できます。

VPSを使っていると急にサービスが停止してしまう事があり、そういう場合httpdのプロセスが残ってしまい再起動しても

(98)Address already in use: make_sock: could not bind to address [::]:80

の様に既に80ポートが使われている為、起動できないというケースが有るので、

lsof -i:80
kill [PID]

の様にプロセスIDをkillすれば起動できるようになります。

/usr/sbin/lsof -i | grep http

のようにして、httpのプロセスIDを表示する方法も有るようです。

WordPressのバージョンアップでサイトが表示できなくなった場合の対処

何気なくWordPressのサイトをバージョンアップしたらサイトが表示できなくなってしまいました。
バックアップから元に戻そうかとも思ったのですが、一応エラーログを確認してみると

61.194.140.210 - - [12/Oct/2012:21:36:54 +0900] "GET / HTTP/1.1" 500 - "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7;
 rv:16.0) Gecko/20100101 Firefox/16.0"

HTTP 500 内部サーバーエラーになっていました。

バージョンアップにより対応してないプラグインがあるため、どうやらエラーになっているらしい。

  1. プラグインディレクトリを退避・プラグインディレクトリを空にする
  2. WordPressの管理画面にログイン
  3. 退避したディレクトリを元に戻す
  4. 管理画面から一つ一つプラグインを有効化し対応してないプラグインを特定する
[root@suusuke ~]# cd /home/blog/public_html/
[root@suusuke public_html]# mkdir wp-content/20121012-plugins
[root@suusuke public_html]# \cp -Rpf wp-content/plugins/* wp-content/20121012-plugins/
[root@suusuke public_html]# rm -Rf wp-content/plugins/*
[root@suusuke public_html]# \cp -Rpf wp-content/20121012-plugins/ wp-content/20121012-plugins/

という順序で解決しました。

Xcode で 既存アプリのアプリ名を変更する方法

Appleからアプリ名が規約違反って事でリジェクトされてしまったので、その時にやったアプリ名を変更する手順をについて書きます。

プロジェクト名の変更

プロジェクト->TARGETSを選択、右のIdentityでProject Nameを新しいアプリ名に変更する。
リネームの確認画面で確認し[Rename]をクリック。

Bundle Identifierの変更

通常であれば、ドメイン名.プロジェクト名 の用な感じで自動で設定されますが、今回はプロジェクト名を新しい名前に変更したので、Bundle Identifierも自動で変わってしまいます。新規でiTunes Connectに申請する場合(App IDをまだ作成してない場合)はいいですが、バージョンアップなので既存のApp IDを指定しました。

Archive Nameの変更

[Edit Scheme…]よりArchive Nameを設定しました。

無事、申請完了しました。

MySQL で テーブルのコピー

ちょっとデータ追加したり、試しにカラム追加したい時等、コピーしてバックアップとっておくとき便利です。

/* aaaテーブルのスキーマをコピーしてテーブル作成 */
> CREATE TABLE back_aaa LIKE aaa;
/* aaaテーブルのデータをINSERT */
> INSERT INTO back_aaa SELECT * FROM aaa;

bitbucketでプロジェクト管理

gitを使ったホスティングサービスだと、githubが一番有名ですが、bitbucketというgitとmercurialも使えるホスティングサービスもあります。

通常であれば、githubでも十分なのですが、bitbucketはプライベートリポジトリが無料で作れるところがいいなと思っています。

Free source code hosting — Bitbucket.

目次

  • bitbucketでプライベートリポジトリを作る
  • ソースコードをpushする

bitbucketでプライベートリポジトリを作る

Privateにチェックをいれて、Repository typeをGitにします。
今回は既存のiPhoneアプリのソースをバージョン管理しようと思うので、LanguageにはObjective-Cを選択します。

ソースコードをpushする

既存のソースコードはgitで管理されてないプロジェクトなので、git initでローカルにリポジトリを作成して、.gitignoreを作成し、バージョン管理しないファイルタイプを設定します。
git remote addでリモートリポジトリを追加します。(今回はoriginという名前で追加)
あとは、ローカルにコミットしてリモートリポジトリにプッシュすれば終了です。

[suusuke@macbook ~]$ cd ~/iPhone/BeamsFlickr/
[suusuke@macbook BeamsFlickr]$ git init
[suusuke@macbook BeamsFlickr]$ vi .gitignore

# hidden/temp files
.DS Store
*.swp
*~.nib
 
# Build dir
build/

# Xcode project files except for the project file
*.xcodeproj/*
!*.xcodeproj/project.pbxproj

# Windows image thumbnail file
Thumbs.db

# User-specific project settings
*.mode1v3
*.mode2v3

[suusuke@macbook BeamsFlickr]$ git remote add origin [httpsのurl]
[suusuke@macbook BeamsFlickr]$ git commit -m "First Commit."
[suusuke@macbook BeamsFlickr]$ git push -u origin master

iTunes Connect ダウンロード数 や 売上ついて

アプリの申請や管理、ダウンロード数などはiTunes Connectから確認することができます。

iTunesConnect_DeveloperGuide_JP.pdf (application/pdf オブジェクト).

ダウンロード数

Sales and Trends から確認できます。

更新されるタイミングは

公式ガイドを確認した結果、

Pacific Time(アメリカ太平洋標準時間)の午前8時に前日分の売上レポート作成されるようです。

となると、日本との時差は17時間だから、

午前1時に前々日分(2日前)の売上レポートが見られることになりますね。

いまはサマータイム実施期間中なので、

午前0時に更新されるかもしれません。

(私は早寝なので確認できませんが・・・)

日次レポートは、

アプリの販売地域が属するタイムゾーンにおいて

午前0時から午後11時59分までの売上を報告しているようです。

Appleが採用しているタイムゾーンは以下の4つ。

Pacific Standard Time (PST)、

Central Europe Time (CET)、

Japan Standard Time (JST)、

Western Standard Time (WST)

この中で一番最後に午後11時59分を迎えるのがアメリカ太平洋標準時間なので

売上レポートの作成はPSTの午前8時に作成される・・・らしいです。

ちなみに、週次レポートは、

月曜日の午前0時から日曜日の午後11時59分までだそうです。

引用元: iPhoneアプリの売上レポートが更新されるタイミング.

売上

Payments and Financial Reportsから確認できます。

有料アプリは値段の70%が開発者に支払われるので500JPY($5.99)の場合は350JPY($4.20)になります。
また、$150を超えないと支払いされないので先は長いですね。

また締め日がいつなのか調べてみたら、Appleと銀行によって決まるみたいで、特に決まってないみたいです。

iPhoneアプリ開発ときどき料理、いちじプログラム: iPhoneアプリの売上の締め日.

売上(Payments and Financial Reports)とダウンロード数(Sales and Trends)があってなくて、売上の詳細を見ると4/1~5/5までとなってて、それだと合うんだけど、ダウンロード数って=売上で良いんだよね。。。

iTunesConnectからアプリダウンロード数レポートを自動取得する方法

iTunesConnectからアプリダウンロード数レポートを自動取得する方法

引用元: iTunesConnectからアプリダウンロード数レポートを自動取得する方法 | zaru blog.

こちらを参考にやってみたので備忘録。

目次

  • jreインストール
  • シェル作成

jreインストール

サーバー環境は、CentOS 5.532bitです。

[root@suusuke ~]# cat /etc/redhat-release 
CentOS release 5.5 (Final)
[root@suusuke ~]# uname -a
Linux suusuke.info 2.6.32-042stab044.11 #1 SMP Wed Dec 14 16:02:00 MSK 2011 i686 i686 i386 GNU/Linux

まずは↓からJREのrpmをダウンロードしてくる。(4/30時点で最新版のjre-7u4-linux-i586.rpmをダウンロード)

Java SE Downloads.

scpやftpでサーバーにアップする。

rpmコマンドでインストールする。

[root@suusuke tmp]# rpm -i jre-7u4-linux-i586.rpm 
[root@suusuke tmp]# java -version
java version "1.7.0_04"
Java(TM) SE Runtime Environment (build 1.7.0_04-b20)
Java HotSpot(TM) Server VM (build 23.0-b21, mixed mode)

シェル作成

jreのインストールが終わったので、iTunesConnectから提供されている、javaのclassファイルを使ってレポートを取得するスクリプトを作成します。
クラスファイルは
http://www.apple.com/itunesnews/docs/Autoingestion.class.zipからダウンロードします。
classファイルをサーバーにアップして、
シェルは冒頭のブログに掲載されているのでそれをちょっとアレンジして

#!/bin/bash

BINDIR=/home/suusuke/bin
DATADIR=/home/suusuke/itunes_report
GETDAY=`date +%Y%m%d -d '2 days ago'`

cd $BINDIR
RESULT=`java -cp "$BINDIR" Autoingestion john@xyz.com letmein 80012345 Sales Daily Summary $GETDAY`
#echo $RESULT;
#exit;
arr=(`echo $RESULT`)
mv ${arr[0]} $DATADIR
gunzip $DATADIR/${arr[0]}

として、取り合えずファイルを保存するだけにしました。

あとはcronに登録して完了です。

45 20 * * * /home/suusuke/bin/get.sh 1> /dev/null

Facebookファンページのいいねしているユーザーを取得する

FQLで簡単に取得できます。

likeテーブル、page_fanテーブルのどちらかで取得できますが、今回はファンページをいいねしているかユーザーをチェックしたかったので、page_fanテーブルを使って取得するようにしました。

SELECT page_id,type,profile_section,created_time FROM page_fan WHERE uid = me() AND page_id = [page_id]

対象のサーバーがPHP4だったのでFacebookのSDKでは対象外なので、JavaScriptで取得するように実装したコードはこちらです。

<!-- facabook like -->
<script type="text/javascript">

  window.fbAsyncInit = function () {
    // init
    FB.init({
      appId: '[appId]',
      status: true,
      cookie: true,
      oauth: true,
      xfbml: true
    });

    // login
    $("#facebook_login").click(function () {
      FB.login(function (resp) {
        if (resp.authResponse) {
          checkLike();
        }
      }, {perms: 'read_stream'});
    });

    var checkLike = function () {
      FB.api(
        {
            method : 'fql.query',
            query : 'SELECT uid,page_id,type,profile_section,created_time FROM page_fan WHERE uid = me() AND page_id = [page_id]'
        },
        function(response) {
            //console.log(response);
            var form = '<form name="regist_form" id="regist" method="post" action="regist.php">';
            var result = 'ng'
            if (response.length > 0 && response[0].page_id == '[page_id]') {
                // ok
                result = 'ok'
                form += '<input type="hidden" name="uid" value="'+response[0].uid+'" />';
            }
            form += '<input type="hidden" name="mode" value="'+result+'" />';
            form += '</form>';
            $('body').append(form);
            $('#regist').submit();

        });
    }

  };

  // javascript SDK load
  $(function () {
    // script create
    (function () {
      var e = document.createElement('script');
      e.src = document.location.protocol + '//connect.facebook.net/ja_JP/all.js';
      e.async = true;
      document.getElementById('fb-root').appendChild(e);
    } ());
  });

</script>
<div id="fb-root"></div>
<!-- facabook like -->

[appId]、[page_id]は任意のものに変更します。

  • パーミッションは”read_stream”にしてログインを要求
  • ログインが成功して、ユーザーが承認したら、FQLで対象のファンページにいいねしているかチェックする
  • いいねしてたら、regist.phpにPOSTする

という流れです。

page_fan – Facebook開発者.