CakePHP5入門【CakePHP5応用編⑪】さまざまな改良

A子

C菜

A子
ウインドウサイズに合わせて縮小表示するのをデフォルトにして、ワンクリックでオリジナルサイズの表示に切り替えられるようにしたいわけよ
どうだろう、できるかな?

C菜
タブで「縮小表示あり」と「縮小表示なし」を切り替えられるようにしたらどうでしょう~?

A子
その案、採用!

B美
「Bulma」のタブ(tabsクラス)ってあくまでもデザインであって、マウスクリックによる切り替え機能は無いわよ

C菜
そうなんですか~?

A子
ヒントをクレメンス

B美
手順は「ChatGPT」が知ってるわ(苦笑)

A子
聞いてみるよ
・・・
まず、「bbsapp/templates/Posts」の中にある「imageview.php」だけどさ
こんな感じに書き換えたよ
<section class="section">
<div class="container"> <a class="buttons" href="" onclick="window.close(); return false;"> <button class="button is-danger is-rounded" style="color: white;">このウィンドウを閉じる</button> </a> <br /> <div class="tabs is-toggle is-toggle-rounded"> <ul id="tab-menu"> <li class="is-active" data-target="tab1"><a>ウィンドウ幅に合わせて縮小</a></li> <li data-target="tab2"><a>オリジナルサイズ</a></li> </ul> </div> <div class="tab-content"> <div id="tab1" class="content is-active"> <div class="viewpanel2"> <img src="<?= $this->Url->build(ltrim($filepath, '.'), ['fullBase' => true]) ?>" /> </div> </div> <div id="tab2" class="content"> <div class="viewpanel"> <img src="<?= $this->Url->build(ltrim($filepath, '.'), ['fullBase' => true]) ?>" /> </div> </div> </div> </div> </section> |


C菜

A子
(「bbsapp/templates/layout」の中にある「image_layout.php」のことね)
あ、赤字で示したクラスが追加したやつよ
<style>
.content { display: none; } .content.is-active { display: block; } .viewpanel { width: 90vw; height: 90vh; overflow: auto; resize: both; display: block; } .viewpanel img { width: auto!important; height: auto!important; max-width: none!important; max-height: none!important; display: block; } .viewpanel2 { width: 90vw; height: 90vh; } .viewpanel2 img { height: auto!important; max-width: 100%!important; } </style> |
さらに同じレイアウトファイル(image_layout.php)の末尾に「script」タグを追加したよ
<script>
document.addEventListener("DOMContentLoaded", function () { const tabs = document.querySelectorAll("#tab-menu li"); const contents = document.querySelectorAll(".tab-content .content"); tabs.forEach(tab => { tab.addEventListener("click", function () { // すべてのタブとコンテンツの `is-active` クラスを削除 tabs.forEach(t => t.classList.remove("is-active")); contents.forEach(c => c.classList.remove("is-active")); // クリックされたタブと対応するコンテンツに `is-active` を追加 tab.classList.add("is-active"); const target = document.getElementById(tab.dataset.target); target.classList.add("is-active"); }); }); }); </script> |
まぁ、ほとんどはChatGPTに教えてもらった通りなんだけどさ(苦笑)



C菜


B美
それじゃ「オリジナルサイズ」のほうをクリックすると…


C菜
ちなみに、掲示板上での縮小表示はこうです~


A子
どうよ(ドヤ顔)

B美

C菜
掲示板の発言内容に対して検索できる機能を付けたいです~

A子
すぐにできそう(笑)

B美
吐いた唾を飲むんじゃないわよ(苦笑)

C菜
B美部長の不敵な笑み…
これって多分、ハマるパターンですよ~

A子
いや、だって単なるデータベース検索だし…
と、とにかく「bbsapp/templates/Posts」の中にある「index.php」に検索ワード入力欄を設けよう
<?= $this->Form->create(null, ['id' => 'search_form', 'url' => ['controller' => 'Posts', 'action' => 'search']]) ?>
検索ワード:<br /> <?= $this->Form->text('search_word', ['id' => 'search_word', 'label' => false, 'div' => false, 'class' => 'input is-info is-normal', 'style' => 'width: 50%;']) ?> <?= $this->Form->button(__('検索'), ['type' => 'submit', 'id' => 'submit_btn', 'class' => 'button is-warning']) ?> <?= $this->Form->end() ?> <br /><br /> |
あ、もともとあったstyleタグはレイアウトファイル(default.php)のほうへ移動したよ
(headタグの内側ね)



C菜


A子
「index.php」の上のほうをちょこちょこっと書き換えて、「search.php」というファイル名で保存しただけ…
(ほとんどは「index.php」と同じってこと)


C菜

A子
public function search()
{ $condition = ['delete_flag' => 0]; $search_word = ''; if ($this->request->is('post')) { $search_word = $this->request->getData('search_word'); if ($search_word != '') { //何らかの検索ワードが指定されている場合、検索条件を追加 $condition += ['body like' => '%'.$search_word.'%']; } } $query = $this->Posts->find()->where($condition); $posts = $this->paginate($query, ['limit' => SEARCH_MAX_PAGE, 'order' => ['id' => 'desc']]); $this->set(compact('posts')); $this->set(compact('search_word')); } |
ポイントは、$conditionという連想配列に検索条件を追加しているところだね
あと、SEARCH_MAX_PAGEという定数を「const.php」の中に追記した
(ついでにバージョンも「1.3.0」に変更)



B美
なかなかやるじゃないの
(一見、問題なさそうに見えるわね)
あ、そうだ!
開発環境だけは「bbsapp/config」の中にある「app_local.php」のdebugパラメータをtrueにしておきなさい
(良い機会だから、DebugKitを使ってみましょう)


C菜
検索ワードには「テスト」って入れて、「検索」ボタンを押してみますね~

A子
我ながらすごいわね
(自画自賛(笑))


B美
でも、「次へ」を押してページを切り替えたらどうなるかしら?

A子
全ての投稿が表示されてる?
(検索ワードが空欄じゃん)


B美

A子


B美


B美


C菜
これってSQL文ですね~
ちゃんとwhere句の中に検索条件が追加されてます~

B美
これにより、本文(body)の中に検索ワード(この例では「テスト」)が含まれているレコードを取り出す…って意味になるわ

A子
便利じゃん
あ、この画面を閉じるときは?

B美

C菜
「次へ」を押してページを切り替えてから、「Sql Log」をもう一度確認してみますね~


A子
なんでだー?

B美
Webページって、複数のページをリンクによって移動できるようになってるわよね?
(そういう文書のことを「ハイパーテキスト」と呼びます)

A子

B美
(言い換えれば、各ページは独立しているってこと)

C菜
でも色々な通販サイトでは、買い物かご(カート)に入れた商品を決済ページまで持ち続けることができますよね~?
あれはページ間を移動する際、パラメータ(購入したい商品)を受け渡してるってことになるのでは~?

B美
そう、あれはそれぞれのWebページから同じ買い物かごを見てるってイメージなんだけど、ページをまたいでパラメータを受け渡しているってことにもなるわね

A子
そもそも会員サイトへのログインなんかでも、ログイン情報をページをまたいで受け渡してるんじゃないの?

B美
さて、それじゃその仕組みを取り入れれば、ページネーションによるページ切り替えを行っても、次のページへ「検索ワード」を受け渡せるってことになるわよね

A子

B美
ただ、割と難しいから基本的なところだけは教えておいてあげるわ
1.コントローラークラスの中にprivateフィールドとして「$session」を宣言します
private $session; |
2.同じファイルの「initialize」メソッド内に
$this->session = $this->request->getSession(); |
を記述します
(これによりセッションを使う準備は完了よ)
3.セッションへの書き込みは「write」メソッド
$this->session->write(['キー' => 値]); |
あ、このメソッドの引数は連想配列なんだけど、複数のパラメータを「一度に」渡す必要はないわよ
$this->session->write(['キー1' => 値1]);
$this->session->write(['キー2' => 値2]); $this->session->write(['キー3' => 値3]); ・・・ |
という感じで、複数のパラメータを順に指定できます
(あとからwriteしたもので前のやつが上書きされることはないってこと)
要するに、複数回のwriteは「上書き」じゃなくて「追加」ってわけ…
あ、「同じキー」を指定した場合は、当然「上書き」になるけどね
4.セッションから読み出すには「read」メソッド
変数 = $this->session->read('キー'); |
5.セッション内にそのキーが存在するかのチェックは「check」メソッド
$this->session->check('キー')
(戻り値はtrueまたはfalse) |
6.セッションキーの削除は「delete」メソッド
$this->session->delete('キー'); |
なお、感覚的には「連想配列」と同じよ
Webページをまたいで、どこからでもアクセス可能な連想配列(みたいなもの)ってイメージかな

C菜

B美
フォーム送信のメソッドは「POST」だけど、リンクをたどったり普通にアクセスする際のメソッドは「GET」よ

A子
んで、POSTじゃないとき(つまり、GETのとき)はセッションから検索ワードを読み出して、それを使って検索条件を追加すると…
public function search()
{ $condition = ['delete_flag' => 0]; $search_word = ''; if ($this->request->is('post')) { $search_word = $this->request->getData('search_word'); if ($search_word != '') { //何らかの検索ワードが指定されている場合、検索条件を追加 $condition += ['body like' => '%'.$search_word.'%']; } //セッションへ書き込み $this->session->write(['search_word' => $search_word]); } else { //セッションから読み出し if ($this->session->check('search_word')) { $search_word = $this->session->read('search_word'); if ($search_word != '') { $condition += ['body like' => '%'.$search_word.'%']; } } } $query = $this->Posts->find()->where($condition); $posts = $this->paginate($query, ['limit' => SEARCH_MAX_PAGE, 'order' => ['id' => 'desc']]); $this->set(compact('posts')); $this->set(compact('search_word')); } |
赤字部分が追加した箇所よ



C菜

B美
Google検索みたいに、複数の単語を空白文字で区切って絞り込み検索できるようにしてみなさい
なお、空白文字については半角でも全角でも同じように処理できるようにすること

A子
$search_word_array = preg_split('/\s+/u', $search_word, -1, PREG_SPLIT_NO_EMPTY); |
で、空白文字(半角または全角)で区切られた複数単語の文字列から配列を作れるらしい
(「正規表現」って言うみたい…よく知らんけど)
で、その配列をforeachで回して、$conditionに対して検索条件を追加していったのがこれ
$condition = [];
$condition[] = ['delete_flag' => 0]; ・・・ foreach ($search_word_array as $word) { $condition[] = ['body LIKE' => '%'.$word.'%']; } |
ポイントは「$condition += [ ]」ではなく、「$condition[] = [ ]」としている点ね
これによって同じキー('body LIKE')を複数混在できるようになる(らしい…知らんけど)


C菜
一応、コピペする人のためにsearchメソッドの最終版をまとめておきますね~
public function search()
{ $condition = []; $condition[] = ['delete_flag' => 0]; $search_word = ''; if ($this->request->is('post')) { $search_word = $this->request->getData('search_word'); if ($search_word != '') { //空白文字(半角または全角)で区切られた複数の単語を配列化 $search_word_array = preg_split('/\s+/u', $search_word, -1, PREG_SPLIT_NO_EMPTY); //検索ワードの数だけ条件を追加 foreach ($search_word_array as $word) { $condition[] = ['body LIKE' => '%'.$word.'%']; } } //セッションへ書き込み $this->session->write(['search_word' => $search_word]); } else { //セッションから読み出し if ($this->session->check('search_word')) { $search_word = $this->session->read('search_word'); if ($search_word != '') { $search_word_array = preg_split('/\s+/u', $search_word, -1, PREG_SPLIT_NO_EMPTY); foreach ($search_word_array as $word) { $condition[] = ['body LIKE' => '%'.$word.'%']; } } } } $query = $this->Posts->find()->where($condition); $posts = $this->paginate($query, ['limit' => SEARCH_MAX_PAGE, 'order' => ['id' => 'desc']]); $this->set(compact('posts')); $this->set(compact('search_word')); } |
赤字の箇所が追加・変更したところです~



B美
今度こそあなたを見直したわ
実戦によって経験値を獲得した結果、レベルが上がったみたいね

A子
てか、値の受け渡しって、セッション以外のやり方ってないの?

B美
それが「GETパラメータ」と呼ばれる、URLの中に値を埋め込む方法ね
(Google検索がまさにこの方法です)

C菜

B美
まぁ、本来の「GETパラメータ」というのは、URLの末尾に「?キー1=値1&キー2=値2&・・・」という形の文字列を連結するやり方だけどね

A子
たしかにGoogleで検索したときって、URLの部分がめっちゃ複雑になるよね

B美
ただ、検索ワードとして指定する文字の種類によっては「URLエンコード」が必要だったり、ちょっと面倒なの
(セッションを利用するほうが多分簡単よ)

C菜
あと、URLがごちゃごちゃしてると見栄えが悪いですしね~

A子