ギジュログ

FlashとかPHPとかiOSとかJavaとか。関東でフリーランスやってる人の気まぐれな技術録。

CakePHP3 もくもく会 #2 に参加してきました 続き

遅くなりましたが、前回の続きです。

前回は投稿の一覧が表示できるようになったところまでをやりました。

投稿の詳細画面を作る

一覧のタイトルをクリックしたらその詳細が確認できるようにします。まずは一覧のタイトルにリンクをつけます。action は view とします。

タイトルを echo してた箇所を下記のように変更します。

-		<td><?php echo $post->title; ?></td>
+		<td><?php echo $this->Html->link($post->title, array('controller' => 'posts', 'action' => 'view', $post->id)); ?></td>

そして、詳細ページのビューを作ります。

<!-- App/Template/Posts/view.ctp -->
<h1><?php echo h($post->title); ?></h1>
<p><small>Created: <?php echo $post->created->format('Y-m-d H:i:s'); ?></small></p>
<p><?php echo h($post->body); ?></p>

コントローラーに view アクションを追加します。このとき、ビューで Html ヘルパーを利用したので、ヘルパーを追加しておきます。このへんの作法は 2 と変わっていません。

// App/Controller/PostsController.php
<?php
// 中略
	
	// ヘルパー追加
	public $helpers = array('Html');

// 中略

	public function view($id = null) {
		if (!$id) {
			throw new NotFoundException(__('Invalid post'));
		}

		$posts = TableRegistry::get('Posts');
		$post = $posts->get($id);
		if (!$id) {
			throw new NotFoundException(__('Invalid post'));
		}
		$this->set('post', $post);
	}

ここまでは前回やった内容とあまり変わりません。

新規エントリーを投稿する

次はエントリーを追加できるようにします。
まずは上記の流れでコントローラー/ビューを作ってしまいます。action は add とします。

ビュー

<!-- App/Template/Posts/add.ctp -->
<h1>Add Post</h1>
<?php
echo $this->Form->create('Post');
echo $this->Form->input('Post.title');
echo $this->Form->input('Post.body', array('rows' => '3'));
echo $this->Form->submit('Save Post');
echo $this->Form->end();
?>

Form ヘルパーは若干挙動が変わっています。 create すると以下のインプットのname属性にはデフォルトで data[モデル名] みたいなプレフィックスが付いていましたが、モデル名はつかなくなっています。Post に関するものは $request->data['Post'] みたいな感じで取れると嬉しいので、とりあえず各要素名の頭に Post. をつけておきました。

それからコントローラーに add アクションを追加します。

// App/Controller/PostsController.php
<?php
// 中略

	// Form 使ったので追加
	public $helpers = array('Html', 'Form');
	// Session コンポーネントを使うので追加
	public $components = array('Session');
	
// 中略

	public function add() {
		if ($this->request->is('post')) {
			$posts = TableRegistry::get('Posts');
			$post = new Post($this->request->data('Post'));
			if ($posts->save($post)) {
				$this->Session->setFlash(__('Your post has been updated.'));
				return $this->redirect(array('action' => 'index'));
			}
			$message = __('Unable to update your post.');
			$this->Session->setFlash($message);
		}
	}

コンポーネントまわりの使い方もとくに変わりはありません。デフォルトのレイアウトにはflashメッセージがあればそれを表示する、みたいな内容が組み込まれているので、Session->setFlash にメッセージをいれると自動的に表示されて便利です。

次はバリデーションを追加します。バリデーションは Table クラスの中で行います。

// App/Model/Table/PostsTable.php
<?php
// 中略

// Validator というクラスを利用します
use Cake\Validation\Validator;

// 中略
	public function validationDefault(Validator $validator) {
		$validator
			->add('title', 'notEmpty', [
				'rule' => 'notEmpty',
				'message' => 'You need to provide a title',
			])
			->add('body', 'notEmpty', [
				'rule' => 'notEmpty',
				'message' => 'A body is required',
			]);
		return $validator;
	}

2系ではモデルのプロパティとして書いていましたが、3系ではコールバックメソッドでバリデーションするようです。コールバックメソッドは validationXXX という形式になります。デフォルトでは validationDefault というコールバックになりますが、保存のときだけ動作して欲しい場合は validationUpdate というコールバックになります。
Table Objects — CakePHP Cookbook 3.x documentation このへんに書いてありました。

これでバリデーションがかかるようになりました。一応補足しておくと、controller で Table->validate() を行っていないのは、デフォルトで save メソッドにてバリデーションが行われていたからです。バリデーションせずに保存するには第二引数に配列で array('validation' => false) を渡すことで可能です。また、設定したエラーメッセージを取得するには、$posts->errors() でメッセージの配列が受け取ることができます。


とりあえず、ここまでがもくもくした内容でした。

CakePHP3 もくもく会 #2 に参加してきました

色々と改善されていると噂の CakePHP 3 をもくもく勉強する会その2にお邪魔してきました。
CakePHP 3.0.0 もくもく会(勉強会) #2 - Co-Edo CakePHP | Doorkeeper
そこでやったことをメモ。

今回の目標

という感じのながれになりました。

環境構築

Co-Edo の田中さんが Vagrantfile を用意してくれていました。(素敵!)ので、それを利用させていただくことに。
Vagrantfile の内容はこちら
コマンドとしては下記のような感じで

# まず VirtualBox の GuestTool を更新するプラグインをいれておく
$ vagrant plugin install vagrant-vbguest
# vagrant を実行する適当なフォルダを作る
$ mkdir -p work/vagrant/cakephp3
$ cd work/vagrant/cakephp3
# 上記のソースファイルをもってくる
$ git clone https://github.com/monsat/vagrant-lamp-sample.git
$ git checkout -b cakephp3 origin/cakephp3
# Vagrantfile あるディレクトリで Vagrant 起動
$ cd vagrant-lamp-sample/vagrant
$ vagrant up

少し時間はかかりますが、これで環境構築は終わりです。うまくいけば http://192.168.33.40/ にアクセスすると下記のようなCakeのページが表示されます。
f:id:katsuren:20140308121725p:plain,w900
DebugKit が入っていないよって警告に関してもグリーンにすることはできますが、Plugin 自体は今動かないようなので今のところ必要ありません。
うまくいかない場合はVMをいったん破壊して起動し直すといいかもしれないです。自分はダウンロードまわりでうまくいかないことが何回かあったのでとりあえずVM削除/起動を繰り返しました。

# うまくいかなかった場合
$ vagrant halt
$ vagrant destroy
y
# もし composer でファイルをダウンロードしてた場合はそれも削除しておく
$ rm -rf ../composer.lock ../app
$ vagrant up




ディレクトリ構成確認

まずはチュートリアルを始める前に、ディレクトリ構成を眺めます。上記の vagrant で起動した場合、vagrant フォルダと同階層の app フォルダがプロジェクト一式になります。
大きな変更点があったところだけざっとピックアップしてみます。早速プロジェクト直下から。

$ tree app -L 1 -d
app
├── App
├── Plugin
├── Test
├── tmp
├── vendor
└── webroot

こんな感じになってます。この時点で色々違います。ちなみに、2 はこんな感じでした。

$ tree cakephp -L 1 -d
cakephp
├── app
├── lib
├── plugins
└── vendors

フォルダ名が大文字になったり、app 内にあった tmp や webroot、Test がプロジェクトフォルダ直下にきてますね。ちなみに、lib フォルダは削除され、vendor の中に cakephp ライブラリとして配置されるようになったようです。

App 内はこんなかんじでした

$ tree App -L 1 -d
App
├── Config
├── Console
├── Controller
├── Lib
├── Locale
├── Model
├── Template
└── View

Template フォルダが増えてます。これ、どうやら View の考え方が変わっていて、今まで View にいれていたテンプレートファイルはこちらで管理するようになったようです。View フォルダは、Helper などのクラスを管理するようになったみたいです。

Config 内も見てみます。

$ tree -L 1 Config/
Config/
├── Schema
├── acl.ini.php
├── acl.php
├── app.default.php
├── app.php
├── bootstrap.php
├── paths.php
└── routes.php

database.php はなくなり、app.php の中で管理するようになったようです。
また、paths.php が新しく追加されました。この中には 2 まで APP とか TMP とか、CakePHPフレームワーク内で定義されていたものがここに移されたようです。独自のビジネスロジックなどをフォルダにまとめて管理する場合に便利そうです。

最後に Model 内を見てみます。

$ tree Model -L 1 -d
Model
├── Behavior
├── Datasource
├── Entity
└── Table

Table と Entity が増えてます。どうやら 3 からは DB とのやりとりをするのは Table、Entity というものが担当するようです。
Table クラスが従来の Model っぽい使い方で、そこから取得したデータは配列ではなく Entity というクラスになって返ってきます。



ブログチュートリアル

CakePHP のドキュメントには、チュートリアルとしてブログを作成する内容のものがあります。3 のバージョンの内容もボチボチ反映されてきていますが、まだそれでは動かなかったりするのでハマりつつその内容を実行してくことに。ドキュメントまわりは下記のものを参考にしました。基本的には 3 の books(英語版)をベースに、API をみつつやっていきました。
CakePHP3 のドキュメント
CakePHP3 の API リファレンス
(すでに CakePHP 3 でブログチュートリアルをやっていらっしゃる方がいました!)
まだ本プレビューでは bake は動かないようなので、自力で全部書いていきます。
成果物は github にあげました。
katsuren/cake3-blog · GitHub


前準備

DB にテーブルを用意しておきます。いつものSQLです。

> CREATE DATABASE cake3_blog DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
> use cake3_blog;
> CREATE TABLE posts (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(50),
    body TEXT,
    created DATETIME DEFAULT NULL,
    modified DATETIME DEFAULT NULL
);
> INSERT INTO posts (title,body,created) VALUES ('タイトル', 'これは、記事の本文です。', NOW());
> INSERT INTO posts (title,body,created) VALUES ('またタイトル', 'そこに本文が続きます。', NOW());
> INSERT INTO posts (title,body,created) VALUES ('タイトルの逆襲', 'こりゃ本当にわくわくする!うそ。', NOW());

設定ファイルは Config/app.php で、"Datasources" という項目名になっているので、それを書き換えます。項目名以外はほとんど変わっていないので、今までどおりな感じです。

<?php

'Datasources' => [
	'default' => [
		'className' => 'Cake\Database\Connection',
		'driver' => 'Cake\Database\Driver\Mysql',
		'persistent' => false,
		'host' => 'localhost',
		'login' => 'your_user_name',  // 設定したユーザー名をいれる
		'password' => 'your_secret',  // 設定したパスワード
		'database' => 'cake3_blog',   // 作成したDB名
		'prefix' => false,
		'encoding' => 'utf8',
	],

	/**
	 * The test connection is used during the test suite.
	 */
	'test' => [
		'className' => 'Cake\Database\Connection'
		'driver' => 'Cake\Database\Driver\Mysql',
		'persistent' => false,
		'host' => 'localhost',
		'login' => 'my_app',
		'password' => 'secret',
		'database' => 'test_myapp',
		'prefix' => false,
		'encoding' => 'utf8',
	],
],

モデルを作成

前準備で作ったテーブルにアクセスするモデルを作成します。とはいっても、今回は Model クラスではなく、Table クラスと Entity クラスを作ることになります。
まずは Table

# App/Model/Table/PostsTable.php
<?php
namespace App\Model\Table;

use Cake\ORM\Table;

class PostsTable extends Table {
}

そしてEntity

# App/Model/Entity/post.php
<?php
namespace App\Model\Entity;

use Cake\ORM\Entity;

class Post extends Entity {
}

とりあえずこれで動くようになります。特筆すべきは、App がなくなったこと。2 系までは利用するクラスを

App::uses('Model', 'Model');

みたいな感じで登録していました。今回は use 文を使うようになっているようです。そのため、2系のプラグインは軒並みアウトですね。それぞれ Table と Entity を継承するので、Cake\ORM\Table と Cake\ORM\Entity を利用宣言します。
また、名前空間を利用するようになっています。とりあえずフォルダ構成と同じ名前空間を指定してあげています。


コントローラー、ビューの作成

では、今作成したモデルから一覧を表示するコントローラー/ビューを作成してみます。

# App/Controller/PostsController.php
<?php
namespace App\Controller;

use App\Controller\AppController;
use Cake\ORM\TableRegistry;

class PostsController extends AppController {
	public function index() {
		$posts = TableRegistry::get('Posts');
		$this->set('posts', $posts->find('all'));
	}
}

コントローラー自体はまだほとんど変わっていませんが、モデルの利用方法が変わっています。いままでは uses プロパティに利用するモデルを記述してモデルを呼び出していました。今回からは TableRegistry というクラスから Table クラスのインスタンスを生成するようです。

<!-- App/Template/Posts/index.ctp -->
<h1>Blog Posts</h1>
<table>
	<tr>
		<th>Id</th>
		<th>Title</th>
		<th>Created</th>
	</tr>
	
	<?php foreach ($posts as $post): ?>
	<tr>
		<td><?php echo $post->id; ?></td>
		<td><?php echo $post->title; ?></td>
		<td><?php echo $post->created->format('Y-m-d H:i:s'); ?></td>
	</tr>
	<?php endforeach; ?>
	<?php unset($post); ?>
</table>

ビューもCTPがそのまま使えます。ここでは Entity の解説をします。
Table から find した結果は Entity の配列として返ってきます。
Entity は、テーブルを作成したときのフィールド名がそのままプロパティとして扱えるので、上記のようにそのまま id, title, created を取得できるようになっています。フィールドの定義が DateTime なので、php でも String ではなく DateTime オブジェクトとして取得されます。

ここまでの内容は、http://192.168.33.40/posts/index で確認することができるようになっています。


ちょっと長くなったので、続きはまたあとで

続き書きました
CakePHP3 もくもく会 #2 に参加してきました 続き - ギジュログ

View-base Application で OpenGL を使う

iOSの開発してて View-base Application で作成したんだけど、一部だけ 3D 処理やんなきゃいけなくなったのでメモ。



iOS で 3D 処理やる場合は大まかに2通り

  • CALayer で擬似的に 3D っぽく見せる
  • OpenGL 使う



一つ目の CALayer

  • UIView なんかをなんちゃらtranslateとかrotateとかっていう CALayer まわりの API を使う。
  • 簡単な3Dの処理を書くのは楽。
  • 複雑な処理はあまり向かない。



二つ目の OpenGL は言うまでもなく、

  • 複雑な描画処理をしてくれるライブラリ。
  • ゴリゴリ回転したりもりもりズームしたりとかをやってくれる。
  • UIView の処理と比べるとコーディングするには敷居が高い
  • 表示しない画像とかを事前に計算して処理を省いたりしてるらしく高速。


通常、OpenGL 使う場合は OpenGL ES テンプレートだったり、OpenGL Game テンプレートだったりを使う。だけど、View-based Application テンプレートを使っていて、一部で OpenGL を利用する方法がわからなかったので調べてみた。


結論から言うと、必要なフレームワークをインポートして、OpenGL の処理をした UIView をビューに追加すればいいだけ。
・・・というだけだと、味気もないし、自分がサイト回って調べてわかんなかったのと一緒なので、もう少し詳しく書く。



手順的は以下

  1. OpenGL のテンプレートを作成する
  2. View-base Application のテンプレートを作成する
  3. View-base Application に OpenGL のテンプレートから作成された EAGLView をコピーする(この時点でOpenGLのテンプレートは削除してもいい)
  4. OpenGLES.framework と QuartzCore.framework をインポートする
  5. EAGLView とそれを表示するための UIView まわりをちょっといじる

こんな感じで View-base Application の一部のビューで OpenGL を利用することができるようになる。



手順を追っていくと、まずは OpenGL と View-base Application のプロジェクト新規作成。これに関しては特に言及する必要はないと思う。便宜的にそれぞれ FooOpenGL プロジェクトと、BarViewbase プロジェクトとする。


次にEAGLViewのコピー。
%FooOpenGL%/Classes 以下に、

  • EAGLView.h
  • EAGLView.m

が生成されていると思う。これがOpenGLの処理が施されたUIViewになるので、こいつを
%BarViewbase%/Classes 以下にコピーする。ターミナルで処理するならこんな感じ。

% cd BarViewbase/Classes
% cp -pR ../../FooOpenGL/Classes/EAGLView* ./

ちなみにEAGLって、Embedded Apple GL の略らしい


次はフレームワークのインポート。
Xcode でBarViewbaseのプロジェクトを開き、左側にあるFrameworksを右クリック。追加→既存のフレームワークを選び、

  • OpenGLES.frameworks
  • QuartzCore.frameworks

を選択する。


最後に、ソースコードをいじる。これが一番はまった。
まずはESGLView.mを編集する。
初期化メソッドを initWithCoder:(NSCoder*)coder ではなく、initWithFrame:(CGRect)frame にする。initWithCoder は xib に登録されたものから呼び出されたときに処理されるものらしい。このへんはまだよくわからない。

- (id)initWithCoder:(NSCoder*)coder
{
	self = [super initWithCoder:coder];
	//.....以降はそのままでよい 
}

こんな感じ↓

- (id)initWithFrame:(CGRect)frame
{
	self = [super initWithFrame:frame];
	//.....以降はそのままでよい 
}

あとは、BarViewbaseViewController.m の loadView あたりで、この EAGLView を生成して、addSubview してあげればいい。その他のOpenGLの処理については FooOpenGLViewController.m を参考にして足りない処理をポチポチ書いていけばきっと動くはず。