. [column] . _existsIn . This value does not exist

Posted by muchag | CakePHP 3.x | 2018-06-16 (土) 17:14:31


公式:データの検証 外部キールール

【環境】
CakePHP: 3.5.11
php: 7.1.5
エラー
. [column] . _existsIn . This value does not exist
原因

外部キーを設定したカラムに、null を代入しようとしたこと。

解決

してない。。。

CakePHP 3.x | 2018-06-16 (土) 17:14:31 |

モデルのないフォーム

Posted by muchag | CakePHP 3.x | 2018-06-10 (日) 12:21:01


公式:モデルのないフォーム

【環境】
CakePHP: 3.5.11
php: 7.1.5
概要

モデルとは関係なく、ユーザ入力を必要とする場合
Form オブジェクトを作成すると、モデルがある場合と同じように
フォームを利用できる。

配置
/src/Form
/plugins/MyVendor/MyPlugin/src/Form
フォームの作成

公式:モデルのないフォーム フォームの作成

  • _buildSchema は FormHelper が HTML フォームを作成する際に使用する スキーマデータを定義するために使います。フィールドの型、長さ、および精度を定義できます。
  • _buildValidator はバリデーターを加えることができる Cake\Validation\Validator のインスタンスを受け取ります。
  • _execute では execute() が呼ばれ、データが有効な時に望むふるまいを定義します。
_buildSchema

addField メソッドで、項目を定義。


protected function _buildSchema(Schema $schema)
{
    return $schema->addField('name', 'string')
        ->addField('email', ['type' => 'string'])
        ->addField('body', ['type' => 'text']);
}
_buildValidator

_buildValidator メソッドで、バリデーションを定義。


protected function _buildValidator(Validator $validator)
{
    return $validator->add('name', 'length', [
            'rule' => ['minLength', 10],
            'message' => '名前は必須です'
        ])->add('email', 'format', [
            'rule' => 'email',
            'message' => '有効なメールアドレスが要求されます',
        ]);
}
利用

下記のようにコントローラからバリデーションだけを利用することもできるみたい。


$contact = new ContactForm();
$isValid = $contact->validate($this->request->getData());
_execute

_execute メソッドに実行処理を定義する?

利用

コントローラから下記のように利用。


$contact = new ContactForm();
$result = $contact->execute($this->request->getData();
フォーム値の設定

公式:モデルのないフォーム フォーム値の設定


$contact = new ContactForm();
if ($this->request->is('post')) {
    if ($contact->execute($this->request->getData())) {
        $this->Flash->success('すぐにご連絡いたします。');
    } else {
        $this->Flash->error('フォーム送信に問題がありました。');
    }
}

if ($this->request->is('get')) {
    // たとえばユーザーモデルの値
    $this->request->data('name', 'John Doe');
    $this->request->data('email','john.doe@example.com');
}

$this->set('contact', $contact);

GET のとき、つまり最初にそのページを訪れたときは、手動で値を設定し
POST のとき、つまりユーザ入力値がある場合は、フォーム値を基に処理を行っている。

フォームエラーの取得

公式:モデルのないフォーム フォームエラーの取得

コントローラーから各フォームフィールドを無効化

公式:モデルのないフォーム コントローラーから各フォームフィールドを無効化
無効化は invalidate の訳で、ここでは「誤っていることを示す」の意味。

CakePHP 3.x | 2018-06-10 (日) 12:21:01 |

save できない

Posted by muchag | CakePHP 3.x | 2018-06-08 (金) 22:17:50


公式:データの保存

【環境】
CakePHP: 3.5.11
php: 7.1.5
事例1
事象

とあるエンティティを保存しようとしたら、保存できない。


object(MyPlugin\Model\Entity\MyEntity) {

	'hoge' => (int) 1,
	'[new]' => true,
	'[accessible]' => [
		'hoge' => true,
	],
	'[dirty]' => [
		'hoge' => true,
	],
	'[original]' => [],
	'[virtual]' => [],
	'[errors]' => [
		'hoge' => [
			'_required' => 'This field is required'
		]
	],
	'[invalid]' => [],
	'[repository]' => 'MyPlugin.MyEntity'

}

hoge に値が入っていて、dirty も true なのに
This field is required

調査

エンティティを吐き出してみたらエラー内容がわかった。


if (! $this->save($entity)) {
    dd($entity);
}
原因

hoge の値をコントローラ側で代入していた。


$entity->hoge = 1;
解決

上記を取りやめ、フォーム側に hidden で持たせたら
無事に保存できた。


echo $this->Form->hidden('hoge', ['value' => 1]);
save できないときのチェックリスト

hideichi.com:[cakephp] saveできない時のチェックリスト(2015-04-01)
→時期的に CakePHP2 の可能性がある。

CakePHP 3.x | 2018-06-08 (金) 22:17:50 |

トランザクション

Posted by muchag | CakePHP 3.x | 2018-06-08 (金) 19:22:03


公式:データベースの基本 トランザクションを使う

【環境】
CakePHP: 3.5.11
php: 7.1.5
覚書

CakePHP3 では、save 処理にトランザクション機能が含まれているので
トランザクションを利用する場合には、save メソッドのトランザクション機能を
明示的にオフにする必要があるとのこと。


//save()単位の原子性を無効に。
$result = $this->Tags->save($tagEntity,['atomic' => false]);

Qiita:CakePHP3でデータベースのトランザクションを使う トランザクションを実装する(2017-08-29)

コネクション
取得

下記、どちらでもよいらしい。

ConnectionManager から直接取得

use Cake\Datasource\ConnectionManager;
$connection = ConnectionManager::get('default');
/config/app.php

'Datasources' => [
    'default' => [

他の設定を取ってきたければ、default の部分を変更。

Table から取得

$connectiopn = $this->Hoge->getConnection();

設定を変更していなければ、どの Table から取得しても OK。

基本

use Cake\Core\Exception\Exception;
use Cake\Datasource\ConnectionManager;

$connection = ConnectionManager::get('default'); // コネクションを取得

$connection->begin(); // トランザクション開始

try {
    if ($this->Hoge->save($hoge)) {
        if ($this->Moge->save($moge)) {
            $connection->commit(); // コミット
        } else {
            throw new Exception();
        }
    } else {
        throw new Exception();
    }
} catch (Exception $exception) {
    $connection->rollback(); //ロールバック
}
ショートカット

Connection オブジェクト には、transactional メソッド が存在する。

  1. begin を呼び出します。
  2. 引数で渡されたクロージャーを実行します。
  3. もしクロージャー内で例外が発生したら、ロールバックを発行して例外を再度 throw します。
  4. クロージャーが false を返したら、ロールバックを発行して false を返します。
  5. クロージャーが正常終了したら、トランザクションをコミットします。

公式:データベースの基本 トランザクションを使う

これは、データ保存の一連の処理を渡すことで、

  1. 例外が発生したら、例外
  2. 戻り値が false ならロールバックをする
  3. それ以外ならコミット

というトランザクション処理のショートカット(?)メソッド。
未試行。


public function insert($hoge, $moge){
    // データ保存の一連の処理をクロージャへ定義
    $saveProcess = function() use($hoge, $moge) {
        if (! $this->Hoge->save($hoge)) {
            return false;
        }

        if (! $this->Moge->save($moge)) {
            return false;
        }

        return true;
    };

    $connectiopn = $this->Hoge->getConnection(); // コネクションを取得
    return $connectiopn->transactional($saveProcess);
}

上記例では、成功時に true を返しているけど
任意の値を返しても OK?

多様性

上記だと、それだけ完結するので
外から呼び出して、他のトランザクションに組み込む方法。
Qiita:CakePHP3でデータベースのトランザクションを使う 別のトランザクションに含められるように改良(2017-08-29)
未試行。


public function insert($hoge, $moge, $atomic = true){
    // データ保存の一連の処理をクロージャへ定義
    $saveProcess = function() use($hoge, $moge) {
        // 同上
    };

    $connectiopn = $this->Hoge->getConnection(); // コネクションを取得

    $result;
    //$atomic = true。つまり単独処理なら処理を行い
    if($atomic){
        $result = $connectiopn->transactional($saveProcess);
    //$atomic = false。つまり他の処理から呼ばれたら保存処理のみを返却
    } else {
        $result = $saveProcess();
    }

    return $result;
}

これはいいね! 8-P
わかりやすく、使いやすそう。

CakePHP 3.x | 2018-06-08 (金) 19:22:03 |

SQL

Posted by muchag | CakePHP 3.x | 2018-06-04 (月) 19:01:42


公式:データベースの基本

【環境】
CakePHP: 3.5.11
php: 7.1.5
発行

下記2種ある。

  • 生の SQL を書く
  • クエリービルダーを利用する
生の SQL
基本

コネクションを取得して、生の SQL をセットして、実行。


use Cake\Datasource\ConnectionManager;

$connection = ConnectionManager::get('default');
$results = $connection->execute('SELECT * FROM articles')->fetchAll('assoc');
プリペアドステートメント
一括指定

プリペアドステートメントを利用する場合には
値を連想配列にして execute メソッドの第2引数として渡す。


$results = $connection
    ->execute('SELECT * FROM articles WHERE id = :id', ['id' => 1])
    ->fetchAll('assoc');
順次指定

(?)疑問符プレースホルダ


$stmt = $conn->prepare(
    'SELECT * FROM articles WHERE published = ? AND created > ?'
);

// 複数項目のバインド
$stmt->bind(
    [true, new DateTime('2013-01-01')],
    ['boolean', 'date']
);

// 1項目ずつのバインド
$stmt->bindValue(1, true, 'boolean');
$stmt->bindValue(2, new DateTime('2013-01-01'), 'date');

名前付きプレースホルダ


$stmt = $conn->prepare(
    'SELECT * FROM articles WHERE published = :published AND created > :created'
);

// 複数項目のバインド
$stmt->bind(
    ['published' => true, 'created' => new DateTime('2013-01-01')],
    ['published' => 'boolean', 'created' => 'date']
);

// 1項目ずつのバインド
$stmt->bindValue('published', true, 'boolean');
$stmt->bindValue('created', new DateTime('2013-01-01'), 'date');
複合データ型

複合データ型(オブジェクト型)の場合は、
必ずデータ型も渡す必要があるのかしらね。。。
未試行。


$results = $connection
    ->execute(
        'SELECT * FROM articles WHERE created >= :created',
        ['created' => new DateTime('1 day ago')],
        ['created' => 'datetime']
    )
    ->fetchAll('assoc');
クエリービルダー

クエリービルダーを利用するとチェーンで記述可能。


$results = $connection
    ->newQuery()
    ->select('*')
    ->from('articles')
    ->where(['created >' => new DateTime('1 day ago'), ['created' => 'datetime']])
    ->order(['title' => 'DESC'])
    ->execute()
    ->fetchAll('assoc');

クエリービルダーを使用する場合は、 execute() メソッドを呼ぶまではサーバーに SQL は 送信されず、メソッド呼び出し後に順次処理されます。
最初に送信してから、順次結果セットを作成します。

公式:データベースの基本 クエリーの実行

後述の項目で検証。

Query, CallbackStatement, ResultSet
  • Query:SQL を生成しただけで、未発行
  • CallbackStatement:SQL を発行済み
  • ResultSet:SQL 発行により得た結果セット(レコードセット)

という理解で合っているのかしら。。。

生の SQL

use Cake\Datasource\ConnectionManager;

$connection = ConnectionManager::get('default');
$results = $connection->execute('SELECT * FROM articles')->fetchAll('assoc');
CallbackStatement

execute までで CallbackStatement を取得。

結果セット

fetchAll(‘assoc’) で、結果セットを配列にて取得。

クエリービルダー

$results = $connection
    ->newQuery()
    ->select('*')
    ->from('articles')
    ->where(['created >' => new DateTime('1 day ago'), ['created' => 'datetime']])
    ->order(['title' => 'DESC'])
    ->execute()
    ->fetchAll('assoc');
Query

->newQuery() で Query を取得。
->order([‘title’ => ‘DESC’]) まで全て Query の設定。

CallbackStatement

->execute() で CallbackStatement を取得。

結果セット

fetchAll(‘assoc’) で、結果セットを配列にて取得。

マジックメソッド

findget というマジックメソッドの場合は、
(用語の使い方が合っているか不明)
そこらに転がっている例を見る限り、少し異なる。


$results = $table // Table オブジェクト内では $this
    ->find()
    ->select('*')
    ->from('articles')
    ->where(['created >' => new DateTime('1 day ago'), ['created' => 'datetime']])
    ->order(['title' => 'DESC'])
    ->all();
Query

->find() で、Query を取得できる。

find メソッドを追っかけていくと
Table オブジェクトの query メソッドにて、
Query オブジェクトを new している。

->order([‘title’ => ‘DESC’]) まで全て Query の設定。

ResultSet

->all() で、ResultSet オブジェクトを取得できる。

あれ?
CallbackStatement はどこへいったのかしらね?

all メソッドの中を覗くと

/vendor/cakephp/cakephp/src/Datasource/QueryTrait.php

$results = $this->_decorateResults($this->_execute());

こういう1行があるので、中で処理を終えてしまっているみたい。

まとめ

execute(find, get 可)しないと、SQL は発行されない。

ただ、Query オブジェクトを取得して、設定を終えておくと
Query オブジェクトを利用する際に、自動的に execute してくれる
と理解してよいのかしらね。

ん~。。。

不確かな情報ばかりで中身は薄いけど、
追っかけた内容を残しておくためのメモ。

CakePHP 3.x | 2018-06-04 (月) 19:01:42 |

フラッシュ

Posted by muchag | CakePHP 3.x | 2018-05-24 (木) 18:34:12


公式:Flash
公式:フラッシュ

【環境】
CakePHP: 3.5.11
php: 7.1.5
概要

FlashComponent にてメッセージをセッションへ格納し
FlashHelper にて、セッションから取り出したメッセージをテンプレートへ出力する。

書式
設定

コントローラ内で、下記のように指定。


$this->Flash->set('This is a message');
出力

テンプレート内で、下記のように指定。


echo $this->Flash->render();

と、公式に書いてあるけど、こんなことを書かなくても出力される。。。
なぜ~。

別名

ちなみに、FlashComponent にてセッションキーを変更した場合は
下記のように独自キーを指定する。


echo $this->Flash->render('other');
エレメント

テンプレートへ出力する際にはエレメントを利用する。

デフォルトのファイルは、下記3種。

/src/Template/Element/Flash/default.ctp
/src/Template/Element/Flash/error.ctp
/src/Template/Element/Flash/success.ctp

※/vendor/cakephp/cakephp/src の方ではなく /app/src の方

FlashComponent

$this->Flash->set('This is a message');
$this->Flash->success('This is a message');
$this->Flash->greatSuccess('This is a message');

上から順番に、下記エレメントを利用して出力される。

/src/Template/Element/Flash/default.ctp
/src/Template/Element/Flash/success.ctp
/src/Template/Element/Flash/great_success.ctp

つまり、メソッド名とエレメント名が連動しており
その名称は独自につけることが可能。

プレフィクス

公式:Flash ルーティングのプレフィックスとフラッシュメッセージ
プレフィクスを利用している場合は、
下記を用意することで、プレフィクス別に設定可能。

/plugins/MyVendor/MyPlugin/src/Template/Prefix/Element/Flash/success.ctp
カスタマイズ
/plugins/MyVendor/MyPlugin/src/Template/Element/Flash/success.ctp

こちらを配置して、中身を書き換えることでカスタマイズできそう。

テーマ

公式:Flash フラッシュメッセージとテーマ
FlashHelper では、テーマを設定できるみたい?

読んでもピンとはこないけど、
時間がある時に試そう。

FlashComponent
オプション
/vendor/cakephp/cakephp/src/Controller/Component/FlashComponent.php

protected $_defaultConfig = [
    'key' => 'flash',
    'element' => 'default',
    'params' => [],
    'clear' => false,
    'duplicate' => true
];

公式で「下記の通りです」と3種並べられている割には、
あちこち見ると増えていく。。。

key デフォルトは ‘flash’。セッション内の ‘Flash’ キー配下の配列キー。
duplicate
clear このキーは bool 値を期待しており、 現在のスタックの全てのメッセージを消去し、新しいものを始めることができます。
element デフォルトは null ですが、 __call() マジックメソッドの使用時には、 自動的に設定されます。表示に使用されるエレメント名。
escape false に設定すると、フラッシュメッセージ中に HTML を出力することができます。
plugin プラグインから指定したエレメントを取得する。
params キーバリューの任意の配列です。エレメントの中で変数として利用する配列。

// コントローラーの中で
$this->Flash->success('The user has been saved', [
    'key' => 'positive',
    'params' => [
        'name' => $user->name,
        'email' => $user->email
    ]
]);
CakePHP 3.x | 2018-05-24 (木) 18:34:12 |

クッキー

Posted by muchag | CakePHP 3.x | 2018-05-23 (水) 0:46:41


公式:クッキー

【環境】
CakePHP: 3.5.11
php: 7.1.5
はじめに

バージョン 3.5.0 で撤廃: クッキーは、 ServerRequest で利用できます。
クッキー をご覧ください。
クッキーの暗号化は クッキー暗号化ミドルウェア をご覧ください。

公式:クッキー

とのことなので、CookieComponent はサヨナラ~。

ただし、
Qiita:CakePHP3.5でCookieComponentが非推奨になり、Cookieの読み書きに苦労した話(2018-03-28)
にも書いてある通り、
クッキーを利用する=クッキー暗号化ミドルウェアを利用しなければならない
ではない。

概要

3.4.x までの CookieComponent に代わって
3.5.x では、クッキーコレクション というのを利用する?

ん~よくわからないけど、あれこれ試した結果
今のところはクッキーコレクションを利用しなくてもクッキーを利用できた。

利用
Cookie オブジェクト
/vendor/cakephp/cakephp/src/Http/Cookie/Cookie.php

public function __construct(
    $name,
    $value = '',
    $expiresAt = null,
    $path = '',
    $domain = '',
    $secure = false,
    $httpOnly = false
)
取得

$cookieValue = $this->request->getCookie('HOGE');
設定

$cookie = new Cookie(
    'HOGE',                     // name(名前)
    'hogehoge',                 // value(値)
    new DateTime('+30 day'),	// expiresAt(期限)
    '/',                        // path(パス)
    'example.com',              // domain(ドメイン名)
    false,                      // secure
    true                        // httpOnly
);

$this->response = $this->response->withCookie($cookie);
削除

$this->response = $this->response->withExpiredCookie('HOGE');
CakePHP 3.x | 2018-05-23 (水) 0:46:41 |

データの削除

Posted by muchag | CakePHP 3.x | 2018-05-19 (土) 19:17:57


公式:データの削除

【環境】
CakePHP: 3.5.11
php: 7.1.5
一括削除

deleteAll メソッドに条件を渡す。


$this->deleteAll(['id' => 1]);
CakePHP 3.x | 2018-05-19 (土) 19:17:57 |

コマンド実行

Posted by muchag | PHP | 2018-05-18 (金) 23:53:31

php からサーバコマンドを実行する方法。

公式:execsystem

概要

php からサーバコマンドを実行するには
exec と system の2つの関数が用意されている。

二者の違いは、出力を返すか返さないかみたいだけど
引数指定次第でどちらも出力を返すみたいなので
結果的には、同じ働きなのかしらね~。

書式
exec

string exec ( string $command [, array &$output [, int &$return_var ]] )

第1引数:コマンド文字列
第2引数:出力格納用変数。省略可。省略すると出力を受け取れない
第3引数:コマンド実行結果ステータス格納用変数。省略可。省略するとステータスを受け取れない

system

string system ( string $command [, int &$return_var ] )

第1引数:コマンド文字列
第2引数:コマンド実行結果ステータス格納用変数。省略可。省略するとステータスを受け取れない

非同期処理

どちらもコマンド文字列に「 > /dev/null &」と付記してやることで、非同期に処理を実行できる。

注意:

プログラムがこの関数で始まる場合、 バックグラウンドで処理を続けさせるには、
プログラムの出力をファイルや別の出力ストリームにリダイレクトする必要があります。
そうしないと、プログラムが実行を終えるまで PHP はハングしてしまいます。

どうやら、上記付記内容は、出力をリダイレクトさせるものらしい。

参考サイト

SAMURAI:【PHP入門】外部コマンドの実行(exec)とコマンドライン実行の方法(2017-10-16)

PHP | 2018-05-18 (金) 23:53:31 |

ロギング

Posted by muchag | CakePHP 3.x | 2018-05-15 (火) 16:46:56


公式:ロギング

【環境】
CakePHP: 3.5.11
php: 7.1.5
設定
/config/app.php

'Log' => [
    'debug' => [
        'className' => 'Cake\Log\Engine\FileLog',
        'path' => LOGS,
        'file' => 'debug',
        'url' => env('LOG_DEBUG_URL', null),
        'scopes' => false,
        'levels' => ['notice', 'info', 'debug'],
    ],
    'error' => [
        'className' => 'Cake\Log\Engine\FileLog',
        'path' => LOGS,
        'file' => 'error',
        'url' => env('LOG_ERROR_URL', null),
        'scopes' => false,
        'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'],
    ],
    // To enable this dedicated query log, you need set your datasource's log flag to true
    'queries' => [
        'className' => 'Cake\Log\Engine\FileLog',
        'path' => LOGS,
        'file' => 'queries',
        'url' => env('LOG_QUERIES_URL', null),
        'scopes' => ['queriesLog'],
    ],
],
レベル

公式:ロギング レベルを使う

Emergency システムは使用出来ません
Alert 今すぐ行動する必要がある
Critical 致命的な状態
Error エラー状態
Warning 警告状態
Notice 正常であるが、重大な状態
Info インフォメーションメッセージ
Debug デバッグレベルメッセージ
出力

クラス内で、


$this->log($message, $level);

// 例
$this->log('オス! おら悟空!', 'info');

とするだけ。

クラスならコントローラやモデルはもちろん
自作も含めて、どこでも使えるのかしら。。。

本体
/vendor/cakephp/cakephp/src/Log/Log.php

こちらを見ても、log メソッドは見つからない。
その代り、各レベル名のメソッドが揃っているので、どこかで変換しているんだろうね。

出力先
/logs
CakePHP 3.x | 2018-05-15 (火) 16:46:56 |
次ページへ »