ウェブインコ

インコの技術メモ

CakePHP3.6, 3.10

ページネートにジョイン (3.10)
例)FROM hoge A LEFT JOIN fuga B ON A.fugaid = B.id
モデル
src/Model/Table/HogeTable.php

    public function initialize(array $config): void
    {
        parent::initialize($config);
//////// 以下を追加
        $this->hasOne('Fuga', [
            'joinType' => 'LEFT OUTER',
            'foreignKey' => 'id',  // 相手
            'bindingKey' => 'fugaid',  // 自分
        ]);
    }
相手のモデルには特に何も書かなくてよい。
hasOne なら 'joinType' => 'INNER', でもよい。
コントローラー
src/Controller/HogeController.php
$options = [
        'conditions' => [ // 条件が必要なら書く。
            'Hoge.honyarara' => 1,
        ],
        'contain' => [ // 相手のテーブル。
            'Fuga'
        ],
        'limit' => 20
];
$dataHpgeFuga = $this->paginate($this->Hoge, $options);


ページネートにジョインした項目でソート (3.10)
コントローラー
src/Controller/HogeController.php

$options = [
        'conditions' => [ // 条件が必要なら書く。
            'Hoge.honyarara' => 1,
        ],
        'contain' => [ // 相手のテーブル。
            'Fuga'
        ],
        'order' => [
            'Fuga.sortkey desc' // asc は省略可
        ],
        'limit' => 20
];
$dataHpgeFuga = $this->paginate($this->Hoge, $options);
ただし、一覧見出しのソートと混ざるとエラーになります。
/vendor/cakephp/cakephp/src/Datasource/Paging/NumericPaginator.php
マージ処理するときのバリデーションで、
URL の引数からの書式は 'hogekey' => 'asc' なのに対し、
ジョイン先の書式は 'Fuga.sortkey asc' なので引っかかります。
試しに、 unset($options['sort']); とか書いてバリデーションをさせず、無理やり以下のように上書すれば希望通りのソートができます。
$options['order'] = array(
    'hogekey'=>'asc',   // URL の引数
    'Fuga.sortkey desc' // ジョインの書式(本来はバリデーションでエラー)
);
マージ前に URL からの引数を渡している場所は以下。 vendor/cakephp/cakephp/src/Controller/Controller.php
L991
        try {
            $results = $paginator->paginate(
                $table,
                $this->request->getQueryParams(), // ← これ
                $settings
            );
どっちもコア部分で、いじるのは良くないし、
困った困った。


ページネートに条件

public function index()
{
    $query = $this->Hogetables->find();
    $query->where(['user_id' => $this->Auth->user('id'), 'status' => 0]);
    $hogetables = $this->paginate($query);
    $this->set(compact('hogetables'));
}


コントローラーでバリデーションエラーを見る
save の後に getErrors() で採れます。
$this->Users ではなく、$user の方です。

if ($this->Users->save($user)) {
    $this->Flash->success(__('The user has been saved.'));
    return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The user could not be saved. Please, try again.'));
error_log("\n----- error -----\n", 3, '/home/logs/log');
error_log(print_r($user->getErrors(), true), 3, '/home/logs/log');
↓ tail -f /home/logs/log ----- error ----- Array ( [username] => Array ( [_isUnique] => This value is already in use ) )


コントローラーからモデルに定義した独自関数を利用する

$vvv = $this->Users->vvv("vvvv");
コントローラーでこんなふうにしたいときは、以下に書きます。
src/Model/Table/UsersTable.php
    public function vvv($data)
    {
        error_log("\n----- $data -----\n", 3, '/home/logs/log');
        return $data;
    }


メール送信
例)
コントローラー

use Cake\Mailer\Email;
~
    $email->setFrom(['from@example.com' => '管理人'])
        ->setTo('to@example.com')
        ->setSubject('題名')
        ->send('本文');
~
// 分けて書く
    $email->setFrom(['from@example.com' => '管理人'])
        ->setTo('to@example.com')
        ->setSubject('題名');
    if ($email->send('本文')) {
        $this->Flash->success('メール送信完了しました。');
    }else{
        $this->Flash->error('メール送信完了できませんでした。');
    }


URL を作成
例)

$url = Router::url(['controller'=>'SentInquiries', 'action'=>'view', $id], true);
// https://webinko.com/sys/sent-inquiries/view/5
$link = '<a href="'.$url.'">'.$url.'</a>';


ビューでオリジナル関数を使う
以下に任意のファイルを置いて書いて呼び出します。
/src/View/Helper/
例)
src/View/Helper/StandardHelper.php

<?php
namespace App\View\Helper;
use Cake\View\Helper;
class StandardHelper extends Helper
{
    // 男女のラジオボタン
    public function radioSex($data=null) {
        $res = array('type' => 'radio', 'options' => array('1'=>'男','2'=>'女','0'=>'その他'), 'legend'=>false);
        if(empty($data['sex'])){
            $res['default'] = 0;
        }else{
            $res['default'] = $data['sex'];
        }
        return $res;
    }
    // 男女の表示
    public function dispSex($num=null) {
        $data = array('その他', '男', '女');
        return $data[$num] ? $data[$num] : 'その他';
    }
}
src/View/AppView.php
<?php
namespace App\View;
use Cake\View\View;
class AppView extends View
{
    public function initialize()
    {
        parent::initialize();
        $this->loadHelper('Standard'); // ← StandardHelper 取り込み
    }
}
src/Template/Users/edit.ctp
<?php echo $this->Form->control('sex', $this->Standard->radioSex($user)); ?>
src/Template/Users/view.ctp
<?= $this->Standard->dispSex($user->sex) ?>
'type' => 'radio' って指定しないとセレクトボックスになります。
<?php echo $this->Form->control('remind_q', ['options' =>  $remind_q]); ?>


コントローラーで他のDBを参照
例)
コントローラーへ以下のように追加。

~
use Cake\ORM\TableRegistry;
~
class UsersController extends AppController
{
    public function initialize()
    {
        parent::initialize();
        $this->Notices = TableRegistry::get('notices');
    }
~
$public_notices =  $this->Notices->find('all', ['conditions' => ['status' => 0, 'public' => 1]]);


DBから1レコードだけでよいとき
[0] とか要らんときは最後に ->first() 付けます。でいいのかな。
例)

$duty = $this->Infos->find('all', ['conditions' => ['filename' => 'duty']])->first();


セレクトボックスのオプション
id をキーにした連想配列。
例)
モデル(src/Model/Table/ResTemplatesTable.php)

    public function initialize(array $config)
    {
~
        $this->setDisplayField('subject'); // list で表示したいカラム
コントローラー
use Cake\ORM\TableRegistry;
~
    public function initialize()
    {
        parent::initialize();
        $this->ResTemplates = TableRegistry::get('res_templates');
~
        $resTemplate = $this->ResTemplates->find('list')->toArray();
ビュー
<?= $this->Form->control('res_template_id', ['options'=>$resTemplate]) ?>
<?php echo $this->Form->control('res_template_id', ['options'=>$resTemplate]); ?>


本体はウェブりたくないとき
シンボリックリンクだと簡単です。
例)
https://ドメイン/sys/ とする場合。

/home/hoge/
  ├ cakephp/webroot/
  └ public_html/
      └ sys -> /home/hoge/cakephp/webroot
コマンド
ln -s /home/hoge/cakephp/webroot /home/hoge/public_html/sys