バリデーションのnotBlank(notEmpty), required, allowEmptyの違い

基本

CakePHPでは、バリデーションはモデルに記述します。以下のように設定できます。

<?php
class Sample extends AppModel {
    public $validate = array(
        'fieldName1' => array(
            'rule'       => 'notBlank',
            'required'   => true,
            'allowEmpty' => false,
            'on'         => 'create',
            'message'    => 'エラーメッセージ'
        )
    );
}
  • notBlank (notEmpty) 空白文字不可。バージョン2.7より古い場合はnotEmpty。

  • required
    trueを指定すると必須項目になる。

  • allowEmpty
    falseを指定すると空白文字不可。

'reuqired' => true

渡された配列にfieldName1が無ければエラーとします。

'ruleName' => 'notBlank' と 'allowEmpty' => false

渡された配列にfieldName1があり、かつ空文字の場合エラーとします。fieldName1自体存在しなければ、バリデーションは実行されずエラーになりません。

この2つを同時に指定する意味はありません。notBlank以外のルールのときに、空文字を許可するかどうかallowEmptyで指定する、という感じです。

ビューの継承とブロック

ビューの継承

共通部分はレイアウトに書くことでビューを細分化できましたが、ビューを親子関係にすることでさらに細分化できます。

app/View/Layouts/default.ctp
app/View/Common/common.ctp
app/View/Sample/foo.ctp app/View/Sample/bar.ctp

という3つのファイルでビューを構成します。一番の親であるレイアウトは以下のようにします。

app/View/Layouts/default.ctp

<!DOCTYPE html>
<html>
<head>
    <title><?php echo $this->fetch('pageTitle') ?></title>
</head>
<body>
    <header>
        <p>Global Header</p>
    </header>
    <?php echo $this->fetch('content') ?>
</body>
</html>

app/View/Common/common.ctpには、レイアウトの中の<?php echo $this->fetch('content') ?>部分を記述します。

app/View/Common/common.ctp

<div class="main">
    <?php echo $this->fetch('content') ?>
</div>
<div class="sub">
     <?php echo $this->fetch('subContent') ?>
</div>

ここでsubContentをfetchするようにしました。このsubContentをブロックといいます。subContentブロックは、app/View/Sample/foo.ctpとapp/View/Sample/bar.ctpで定義します。
この2つのファイルでは、app/View/Common/common.ctpの<?php echo $this->fetch('content') ?>部分を記述します。

app/View/Sample/foo.ctp

<?php
$this->extend('Common/common');
$this->assign('pageTitle', 'foo title');
?>

<?php $this->start('subContent'); ?>
<p>foo subContent</p>
<?php $this->end(); ?>

<p> foo mainContent</p>

extend('Common/common')で継承するビューを指定します。
start('subContent')でブロックを開始し、end()で終了します。
どのブロックにも属さない部分は、自動でcontentブロックになります。

同じようにもうひとつのビューファイルも作ってみます。

app/View/Sample/bar.ctp

<?php
$this->extend('Common/common');
$this->assign('pageTitle', 'bar title');
?>

<?php $this->start('subContent'); ?>
<p>bar subContent</p>
<?php $this->end(); ?>

<p> bar mainContent</p>

ブラウザでアクセスできるように、Sampleコントローラにfooアクション、barアクションを実装してみます。結果は以下になります。

f:id:myhateno:20180529122645p:plain

/sample/barにアクセスすると、ページ内容がbarのものに変わっています。

ビューとレイアウト

基本

CakePHPのビューはいくつかのレイヤーに分かれています。

  • レイアウト 大枠。ページの共通部分。
  • ビュー 各アクションに対応して表示するページ。
  • エレメント 各ビューで使いまわす部分。

このうちビューとエレメントについて説明します。

レイアウト

まずはレイアウトで、ページの共通部分をコーディングします。
レイアウトはいくらでも作ることができ、各コントローラやアクションのビューでどのレイアウトを使うか指定できます。指定しない場合、app/View/Layouts/default.ctpになります。
default.ctpには既に色々書かれていますが、自分で書き直してみます。

app/View/Layouts/default.ctp

<!DOCTYPE html>
<html>
<head>
     <title>default.ctpを使ってます</title>
     <link rel="stylesheet" href="common.css">
</head>
<body>
    <header>ヘッダー</header>
    <?php echo $this->fetch('content') ?>
    <footer>フッター</footer>
</body>
</html>

上記の<?php echo $this->fetch('content') ?>は、ビューの内容を表示する記述です。適当なビューapp/View/Sample/index.ctpを作ってみます。

app/View/Sample/index.ctp

<h1>sample page</h1>
<p>サンプルのビューです</p>

コントローラとアクションも作ってブラウザでアクセスしてみます。

app/Controller/SampleController.php

<?php
class SampleController extends AppController {
    public function index() {}
}

結果

f:id:myhateno:20180529111623p:plain

ビューから変数を渡す

レイアウトを以下に書き換えます。

app/View/Layouts/default.ctp

<!DOCTYPE html>
<html>
<head>
     <title><?php echo $this->fetch('pageTitle') ?></title>
     <link rel="stylesheet" href="common.css">
</head>
<body>
    <header><?php echo $this->fetch('headerTitle') ?></header>
    <?php echo $this->fetch('content') ?>
    <footer>フッター</footer>
</body>
</html>

fetch('pageTitle')やfetch('headerTitle')でビューから渡される変数を取得します。なおビューから渡されないと空文字になります。
ビューを以下のよう変更しましょう。

app/View/Sample/index.ctp

<?php
$this->assign('pageTitle', 'page title | samplesite');
$this->assign('headerTitle', 'sample page header');
?>

<h1>sample page</h1>
<p>サンプルのビューです</p>

assignメソッドで各変数に値をセットしています。ブラウザで見ると

f:id:myhateno:20180529112655p:plain

異なるビューを作ったとき、assign('pageTitle', 'new title')などにすれば、各ビューからレイアウトへ異なる値を渡すことができます。

コントローラから変数を渡す

setメソッドを使って、コントローラから変数を渡すこともできます。

app/Contoller/SampleController.php

<?php
class SampleController extends AppController {
    public function index() {
        $this->set(array(
            'pageTitle' => 'page title | samplesite',
            'headerTitle' => 'sample page header',
            'text' => 'dummy text'
        ));
    }
}

コントローラから渡された変数はfetchメソッドではなくそのまま変数名でアクセスできます。

app/View/Layouts/default.ctp

<!DOCTYPE html>
<html>
<head>
     <title><?php echo $pageTitle ?></title>
     <link rel="stylesheet" href="common.css">
</head>
<body>
    <header><?php echo $headerTitle ?></header>
    <?php echo $this->fetch('content') ?>
    <footer>フッター</footer>
</body>
</html>

app/View/Sample/index.ctp

<h1>sample page</h1>
<p><?php echo $text ?></p>

ブラウザで見ると

f:id:myhateno:20180529113559p:plain

使用するレイアウトを変更する

コントローラかビューから、$this->layout = 'レイアウト名'で変更できます。

<?php
// コントローラから
public function index() {
    $this->layout = 'sample';
}

// ビューファイルから
$this->layout = 'sample';

多対多(HABTM)のアソシエーション

公式:
アソシエーション: モデル同士を繋ぐ - 2.x

アソシエーションの意味

データを取得する時に、関連するモデルの情報も持ってきてくれます。
コントローラでfind('all')したときに取得される結果が以下ですが、[AssociatedModelName]の値が関連モデルです。

Array
(
    [0] => Array
        (
            [ModelName] => Array
                (
                    [id] => 83
                    [field1] => value1
                    [field2] => value2
                    [field3] => value3
                )

            [AssociatedModelName] => Array
                (
                    [id] => 1
                    [field1] => value1
                    [field2] => value2
                    [field3] => value3
                )

        )
)

どのモデルにアソシエーションを設定するか

トランザクションテーブルのモデルに設定するとよい?もちろんマスターテーブルのモデルにも設定できるが、あまり必要な場面はなさそう?

テーブル構成を決める

例として、アンケートをフォームで受け付け結果をデータベースに格納することを考えます。アンケート内容は以下です。

※全て回答必須

項目名 内容
名前 氏名を教えてください。
年齢 年齢を教えてください。
趣味 次から選択してください(複数選択可)。
読書、音楽鑑賞、映画鑑賞、旅行、自転車、写真、その他

多対多のテーブル

まず趣味マスターとしてhobbiesテーブルを作ります。

id name
1 読書
2 音楽鑑賞
3 映画鑑賞
4 旅行
5 自転車
6 写真
7 その他

アンケート結果を格納するentriesテーブルを作ります。

id name age hobby
1 田中太郎 31 1, 5, 7
2 山田花子 20 1, 4
3 高橋次郎 28 3, 7
4 鈴木裕子 39 3

このとき、hobbiesとentriesは多対多(HABTM)といえます。
hobbiesの1つのレコードから見て、entriesにはたくさん対応するレコードがあります。
entriesの1つのレコードから見て、hobbiesにはたくさん対応するレコードがあります(id4鈴木さんのように一つしか対応しないものもあります)。

正規化

上のentriesテーブルは、1つのレコードに1つの値というデータベースの原則に沿っていません。多対多の時は、中間テーブルを作ってその原則に沿えるようにします(これを正規化といいます)。
中間テーブルentries_hobbiesを作り、以下のようにします。

entries

id name age
1 田中太郎 31
2 山田花子 20
3 高橋次郎 28
4 鈴木裕子 39

entries_hobbies

id entry_id hobby_id
1 1 1
2 1 5
3 1 7
4 2 1
5 2 4
6 3 3
7 3 7
8 4 3

これで1レコードに1値になりました。まとめたER図は以下です。
なおCakePHPは複合主キーに対応していない?ため、entries_hobbiesにもidという主キーを追加しています。

f:id:myhateno:20180525125216p:plain

CakePHPでアソシエーションを設定する

やっと本題です。以上のテーブル構成をアソシエーションとして、Entryモデルに設定します。

app/Model/Entry.php

<?php
class Entry extends AppModel {
    public $hasAndBelongsToMany = array(
        'Hobby' =>
            array(
                'joinTable' => 'entries_hobbies'
            )
    );
}

Entryにとって、$hasAndBelongsToMany(多対多)がHobby。
そのjoinTable(中間テーブル)がentries_hobbiesとCakePHPに教えています。
なおentries_hobbiesはCakePHPの中間テーブルの命名規則にそっているため、以下のようにjoinTableは教えなくてもOKです。

<?php
public $hasAndBelongsToMany = array(
    'Hobby'
);

これでEntryモデルをfind('all')したときに、hobbiesテーブルの情報もついてきます。

Scalaのクラス

参考サイト
Scalaクラスメモ(Hishidama's Scala class Memo)

コンストラクタ

コンストラクタとは、インスタンス化するときの処理をまとめた特別なメソッドです。
インスタンス化とは、クラス(設計図)からインスタンス(組み立てられた実物)を作ることです。

scalaのコンストラクタはクラス内に直接書きます。
以下はnameフィールドとgreetメソッドを定義しています。
フィールドは状態、メソッドは行動を保持し、その2つをメンバーと呼びます。

class Person(str: String) {
  val name = str
  def greet(): Unit = println(s"私の名前は${name}です。")
}

val taro = new Person("太郎")

scala> taro.name
res0: String = 太郎

scala> taro.greet
私の名前は太郎です。

コンストラクタ引数を設定しているのに与えないとエラーとなります。

scala> val hanako = new Person
<console>:12: error: not enough arguments for constructor Person: (name: String)Person.
Unspecified value parameter name.

val name = strはコンストラクタ引数にvalを与えることで省略できます。

class Person(val name: String) {
  def greet(): Unit = println(s"私の名前は${name}です。")
}

val taro = new Person("太郎")

scala> taro.name
res0: String = 太郎

scala> taro.greet
私の名前は太郎です。

valを与えないと非公開フィールドになります。

class Person(name: String) {
  def greet(): Unit = println(s"私の名前は${name}です。")
}

val taro = new Person("太郎")

scala> taro.name
<console>:13: error: value name is not a member of Person

アクセス修飾子

アクセス修飾子をdef, val, varの前に付けることで、非公開メンバーにすることができます。

class Person(name: String) {
  private def greet(): Unit = println(s"私の名前は${name}です。")
}

val taro = new Person("太郎")

scala> taro.greet
<console>:13: error: method greet in class Person cannot be accessed in Person

継承

extendsキーワードを使い継承すると、親クラス(継承したクラス)のメンバーを利用できます。親クラスのメソッドを上書きするときはoverrideキーワードを付与します。

継承したときは、親クラスのコンストラクタ引数もちゃんと指定します。

class Person(name: String) {
  def run(): Unit = println(s"${name}は走った!")
  def greet(): Unit = println(s"私の名前は${name}です。")
}

// Personを継承しているのに、Personのコンストラクタに値を渡していないと・・・
// class Student(name: String, grade: Int) extends Person

// 以下のエラーになります
//  not enough arguments for constructor Person: (name: String)Person.

// extends Person(name)として、値を渡します
class Student(name: String, grade: Int) extends Person(name) {
  override def greet(): Unit = println(s"私の名前は${name}で${grade}年生です。")
}

val s = new Student("yamada", 6)

scala> s.run
yamadaは走った!

scala> s.greet
私の名前はyamadaで6年生です。

親と子クラスに同じ名前の公開フィールドがあると衝突しますので、overrideを付与するか、親クラスに渡す引数名を変更しましょう。

class Person(val name: String) {
  def greet(): Unit = println(s"私の名前は${name}です。")
}

// nameがPersonクラスのnameと被っていると
// class Student(val name: String, val grade: Int) extends Person(name) {
//   override def greet(): Unit = println(s"私の名前は${name}で${grade}年生です。")
// }

// 以下のエラーになります。
// error: overriding value name in class Person of type String;

// それを避けるため、overrideを付与するか

class Student(override val name: String, val grade: Int) extends Person(name) {
  override def greet(): Unit = println(s"私の名前は${name}で${grade}年生です。")
}

// または、引数名を変えましょう。

class Student(val _name: String, val grade: Int) extends Person(_name) {
  override def greet(): Unit = println(s"私の名前は${name}で${grade}年生です。")
}