- 投稿日:2020-04-06T23:19:06+09:00
【WordPress】ACFで、『投稿以外』でチェックボックス・セレクトボックスの値とラベルを全て表示 | get_field_object(っぽいもの)を使う【Advanced Custom Fields】
WordPressを使用した企業サイトで、「管理画面のカスタム投稿一覧で、カスタムフィールドを使った絞り込み検索がしたい」というオーダーがあった時の話。
カスタムタクソノミーや作成者での絞り込み検索の実装は過去に経験があったので、その要領でいけるだろうと考えていたら意外なところで詰まったので備忘録として。要件
- カスタムフィールドの実装
- 管理画面のカスタム投稿一覧で、カスタムフィールドで絞り込み検索できるようにする
実装するカスタムフィールドはこんな感じ。
よくある簡単なセレクトボックス。
実装
ひとまず、管理一覧画面に自前の検索用セレクトボックスを追加するには
restrict_manage_posts
を使ってよしなにコードを書けばよい。で、ACFには
get_field_object
という、カスタムフィールドの名前からそのフィールドの情報を引っ張ってくる便利な関数があるので、それを使えばまあ何とかなるだろうと考えて以下のようなコードを書いてみた。
上のテスト用フィールドではセレクトボックスのフィールドタイプなので、get_field_object('カスタムフィールドの名前')['choices']
で選択肢の値とラベルのセットを取得できる。function.phpfunction add_custom_edit_post_search_form( $post_type ){ if ( $post_type === 'カスタム投稿タイプのスラッグ' ){ $selected = get_query_var('test') ? get_query_var('test') : ''; $field_obj = get_field_object('test'); printf('<select name="%s">', $field_obj); printf('<option value="">すべての%s</option>', $field_obj['label']); foreach($field_obj['choices'] as $value => $label){ $selected_text = ''; if($selected === $value) { $selected_text = ' selected'; } printf('<option value="%s"%s>%s</option>', $value, $selected_text, $label); } printf('</select>'); } }; add_action('restrict_manage_posts', 'add_custom_edit_post_search_form', 10, 1); // 実際は`query_vars`や`pre_get_posts`等と組み合わせて、実際に投稿を絞り込まないと意味はないが、 // 本筋の話ではないためここでは省くこれでいける…かと思いきや、選択したフィールド値を持つ投稿が1件もない場合(要するに「見つかりませんでした」の時)にエラーになる。
色々と調べてみると、
get_field_object
は引数に投稿IDを取っており、投稿に対してしか使えない。
何とも困ったので調べてみると同じ状況に陥っている人がいたようで、フィールドグループのpost_IDからフィールド情報を引っ張ってくるやり方を紹介しているページを見つけた。https://www.healing-solutions.jp/tech/4298/
上記は2017年の記事で、ACFの仕様が変わったのか残念ながら上記のやり方ではできなかったが、「フィールド情報をカスタム投稿として保存しているなら、直接
get_posts()
」すればいけるのでは?」と思い、
さらに調べてみたら、フィールドグループは"acf-field-group"
、その中のフィールドは"acf-field"
という投稿タイプのポストデータとしてwp_posts
テーブルに保存されていた。(上がフィールドグループの投稿データ、下がフィールド名「test」の投稿データ)
データの中身
上記画像を見て分かる通り、
get_field_object
で取得する際の['label']
や['name']
は、それぞれpost_title
やpost_name
に保存されている。
get_field_object
で取得できるのは、以下のような感じ。
※一部はダミーテキストと差し替えていますvar_dump(get_field_object('test'))var_dump(get_field_object('test')); // 出力 array(25) { ["ID"]=> int(661) ["key"]=> string(19) "field_*************" ["label"]=> string(9) "テスト" ["name"]=> string(4) "test" ["prefix"]=> string(3) "acf" ["type"]=> string(6) "select" ["value"]=> string(4) "hoge" ["menu_order"]=> int(0) ["instructions"]=> string(27) "これはテストです。" ["required"]=> int(1) ["id"]=> string(0) "" ["class"]=> string(0) "" ["conditional_logic"]=> int(0) ["parent"]=> int(660) ["wrapper"]=> array(3) { ["width"]=> string(0) "" ["class"]=> string(0) "" ["id"]=> string(0) "" } ["choices"]=> array(6) { ["hoge"]=> string(6) "ほげ" ["fuga"]=> string(6) "ふが" ["piyo"]=> string(6) "ぴよ" ["foo"]=> string(6) "ふー" ["bar"]=> string(6) "ばー" ["baz"]=> string(6) "ばず" } ["default_value"]=> array(1) { [0]=> string(4) "hoge" } ["allow_null"]=> int(0) ["multiple"]=> int(0) ["ui"]=> int(1) ["ajax"]=> int(1) ["return_format"]=> string(5) "value" ["placeholder"]=> string(0) "" ["_name"]=> string(4) "test" ["_valid"]=> int(1) }
対して、以下は
get_posts()
で取得できる投稿データ。
※一部はダミーテキストと差し替えていますvar_dump(get_posts())$args = array( 'post_excerpt' => 'test', // 下記の出力内容を見ると分かるが、フィールド名は抜粋として保存されている 'post_type' => 'acf-field', ); $acf_fields = get_posts( $args )[0]; // post_contentの中身はシリアライズ化されているので、もとに戻す。 $acf_fields->post_content = unserialize($acf_fields->post_content); var_dump($acf_fields); // 出力 object(WP_Post)#9316 (24) { ["ID"]=> int(661) ["post_author"]=> string(1) "1" ["post_date"]=> string(19) "2020-**-** **:**:**" ["post_date_gmt"]=> string(19) "2020-**-** **:**:**" ["post_content"]=> array(13) { ["type"]=> string(6) "select" ["instructions"]=> string(27) "これはテストです。" ["required"]=> int(1) ["conditional_logic"]=> int(0) ["wrapper"]=> array(3) { ["width"]=> string(0) "" ["class"]=> string(0) "" ["id"]=> string(0) "" } ["choices"]=> array(6) { ["hoge"]=> string(6) "ほげ" ["fuga"]=> string(6) "ふが" ["piyo"]=> string(6) "ぴよ" ["foo"]=> string(6) "ふー" ["bar"]=> string(6) "ばー" ["baz"]=> string(6) "ばず" } ["default_value"]=> array(1) { [0]=> string(4) "hoge" } ["allow_null"]=> int(0) ["multiple"]=> int(0) ["ui"]=> int(1) ["ajax"]=> int(1) ["return_format"]=> string(5) "value" ["placeholder"]=> string(0) "" } ["post_title"]=> string(9) "テスト" ["post_excerpt"]=> string(4) "test" ["post_status"]=> string(7) "publish" ["comment_status"]=> string(6) "closed" ["ping_status"]=> string(6) "closed" ["post_password"]=> string(0) "" ["post_name"]=> string(19) "field_*************" ["to_ping"]=> string(0) "" ["pinged"]=> string(0) "" ["post_modified"]=> string(19) "2020-**-** **:**:**" ["post_modified_gmt"]=> string(19) "2020-**-** **:**:**" ["post_content_filtered"]=> string(0) "" ["post_parent"]=> int(660) ["guid"]=> string(54) "https://www.example.com/?post_type=acf-field&p=661" ["menu_order"]=> int(0) ["post_type"]=> string(9) "acf-field" ["post_mime_type"]=> string(0) "" ["comment_count"]=> string(1) "0" ["filter"]=> string(3) "raw" }対応表
あまり使わないであろうものは省略。
$field = get_field_object()
→ $field = get_posts()
$field["ID"]
→ $field->ID
$field["key"]
→ $field->post_name
$field["label"]
→ $field->post_title
$field["name"]
→ $field->post_excerpt
$field["type"]
→ $field->post_content["type"]
$field["instructions"]
→ $field->post_content["instructions"]
$field["choices"]
→ $field->post_content["choices"]
$field["default_value"]
→ $field->post_content["default_value"]
$field["placeholder"]
→ $field->post_content["placeholder"]
$field["wrapper"]['id']
→ $field->post_content["wrapper"]['id']
フィールド名は
post_excerpt
、フィールドラベルはpost_title
、
その他のだいたいのデータはpost_content
内にシリアライズ化されて入っている模様。
最終的な実装
上記の表を踏まえて、
get_posts()
で取得したオブジェクトから適宜データを引っ張ってこればよい。
先にも書いたとおりpost_content
の中身はシリアライズ化されているため、unserialize()
するのを忘れないように注意。function.phpfunction add_custom_edit_post_search_form( $post_type ){ if ( $post_type === 'カスタム投稿タイプのスラッグ' ){ $selected = get_query_var('test') ? get_query_var('test') : ''; args = array( 'post_excerpt' => 'test', //'name' => 'field_*************', あまり無いとは思うが、同じ名前のフィールドを複数使っている場合はフィールドキーで取得するほうが良い //'parent' => ***, もしくは、フィールドグループが親投稿として指定されているのでそれと組み合わせる 'post_type' => 'acf-field', ); $field_obj = get_posts( $args )[0]; $field_obj->post_content = unserialize($field_obj->post_content); printf('<select name="%s">', $field_obj->post_excerpt); printf('<option value="">すべての%s</option>', $field_obj->post_title); foreach($field_obj->post_content['choices'] as $value => $label){ $selected_text = ''; if($selected === $value) { $selected_text = ' selected'; } printf('<option value="%s"%s>%s</option>', $value, $selected_text, $label); } printf('</select>'); } }; add_action('restrict_manage_posts', 'add_custom_edit_post_search_form', 10, 1);
管理画面以外でも、たとえばサイドバー等に検索用のセレクトボックスを表示したいときなど、何かしらループ外で処理したいときに役に立つと思われる。
- 投稿日:2020-04-06T23:19:06+09:00
【WordPress】ACFで、『投稿以外』でチェックボックスやセレクトボックスの値とラベルを全て取得 | get_field_object(っぽいもの)を使う【Advanced Custom Fields】
WordPressを使用した企業サイトで、「管理画面のカスタム投稿一覧で、カスタムフィールドを使った絞り込み検索がしたい」というオーダーがあった時の話。
カスタムタクソノミーや作成者での絞り込み検索の実装は過去に経験があったので、その要領でいけるだろうと考えていたら意外なところで詰まったので備忘録として。要件
- カスタムフィールドの実装
- 管理画面のカスタム投稿一覧で、カスタムフィールドで絞り込み検索できるようにする
実装するカスタムフィールドはこんな感じ。
よくある簡単なセレクトボックス。
実装
ひとまず、管理一覧画面に自前の検索用セレクトボックスを追加するには
restrict_manage_posts
を使ってよしなにコードを書けばよい。で、ACFには
get_field_object
という、カスタムフィールドの名前からそのフィールドの情報を引っ張ってくる便利な関数があるので、それを使えばまあ何とかなるだろうと考えて以下のようなコードを書いてみた。
上のテスト用フィールドではセレクトボックスのフィールドタイプなので、get_field_object('カスタムフィールドの名前')['choices']
で選択肢の値とラベルのセットを取得できる。function.phpfunction add_custom_edit_post_search_form( $post_type ){ if ( $post_type === 'カスタム投稿タイプのスラッグ' ){ $selected = get_query_var('test') ? get_query_var('test') : ''; $field_obj = get_field_object('test'); printf('<select name="%s">', $field_obj); printf('<option value="">すべての%s</option>', $field_obj['label']); foreach($field_obj['choices'] as $value => $label){ $selected_text = ''; if($selected === $value) { $selected_text = ' selected'; } printf('<option value="%s"%s>%s</option>', $value, $selected_text, $label); } printf('</select>'); } }; add_action('restrict_manage_posts', 'add_custom_edit_post_search_form', 10, 1); // 実際は`query_vars`や`pre_get_posts`等と組み合わせて、実際に投稿を絞り込まないと意味はないが、 // 本筋の話ではないためここでは省くこれでいける…かと思いきや、選択したフィールド値を持つ投稿が1件もない場合(要するに「見つかりませんでした」の時)にエラーになる。
色々と調べてみると、
get_field_object
は引数に投稿IDを取っており、投稿に対してしか使えない。
何とも困ったので調べてみると同じ状況に陥っている人がいたようで、フィールドグループのpost_IDからフィールド情報を引っ張ってくるやり方を紹介しているページを見つけた。https://www.healing-solutions.jp/tech/4298/
上記は2017年の記事で、ACFの仕様が変わったのか残念ながら上記のやり方ではできなかったが、「フィールド情報をカスタム投稿として保存しているなら、直接
get_posts()
すればいけるのでは?」と思い、
さらに調べてみたら、フィールドグループは"acf-field-group"
、その中のフィールドは"acf-field"
という投稿タイプのポストデータとしてwp_posts
テーブルに保存されていた。(上がフィールドグループの投稿データ、下がフィールド名「test」の投稿データ)
データの中身
上記画像を見て分かる通り、
get_field_object
で取得する際の['label']
や['name']
は、それぞれpost_title
やpost_excerpt
に保存されている。
get_field_object
で取得できるのは、以下のような感じ。
※一部はダミーテキストと差し替えていますvar_dump(get_field_object('test'))var_dump(get_field_object('test')); // 出力 array(25) { ["ID"]=> int(661) ["key"]=> string(19) "field_*************" ["label"]=> string(9) "テスト" ["name"]=> string(4) "test" ["prefix"]=> string(3) "acf" ["type"]=> string(6) "select" ["value"]=> string(4) "hoge" ["menu_order"]=> int(0) ["instructions"]=> string(27) "これはテストです。" ["required"]=> int(1) ["id"]=> string(0) "" ["class"]=> string(0) "" ["conditional_logic"]=> int(0) ["parent"]=> int(660) ["wrapper"]=> array(3) { ["width"]=> string(0) "" ["class"]=> string(0) "" ["id"]=> string(0) "" } ["choices"]=> array(6) { ["hoge"]=> string(6) "ほげ" ["fuga"]=> string(6) "ふが" ["piyo"]=> string(6) "ぴよ" ["foo"]=> string(6) "ふー" ["bar"]=> string(6) "ばー" ["baz"]=> string(6) "ばず" } ["default_value"]=> array(1) { [0]=> string(4) "hoge" } ["allow_null"]=> int(0) ["multiple"]=> int(0) ["ui"]=> int(1) ["ajax"]=> int(1) ["return_format"]=> string(5) "value" ["placeholder"]=> string(0) "" ["_name"]=> string(4) "test" ["_valid"]=> int(1) }
対して、以下は
get_posts()
で取得できる投稿データ。
※一部はダミーテキストと差し替えていますvar_dump(get_posts())$args = array( 's' => 'test', // フィールド名は抜粋に入っているため、キーワード検索で良い 'post_type' => 'acf-field', ); $acf_fields = get_posts( $args )[0]; // post_contentの中身はシリアライズ化されているので、もとに戻す。 $acf_fields->post_content = unserialize($acf_fields->post_content); var_dump($acf_fields); // 出力 object(WP_Post)#9316 (24) { ["ID"]=> int(661) ["post_author"]=> string(1) "1" ["post_date"]=> string(19) "2020-**-** **:**:**" ["post_date_gmt"]=> string(19) "2020-**-** **:**:**" ["post_content"]=> array(13) { ["type"]=> string(6) "select" ["instructions"]=> string(27) "これはテストです。" ["required"]=> int(1) ["conditional_logic"]=> int(0) ["wrapper"]=> array(3) { ["width"]=> string(0) "" ["class"]=> string(0) "" ["id"]=> string(0) "" } ["choices"]=> array(6) { ["hoge"]=> string(6) "ほげ" ["fuga"]=> string(6) "ふが" ["piyo"]=> string(6) "ぴよ" ["foo"]=> string(6) "ふー" ["bar"]=> string(6) "ばー" ["baz"]=> string(6) "ばず" } ["default_value"]=> array(1) { [0]=> string(4) "hoge" } ["allow_null"]=> int(0) ["multiple"]=> int(0) ["ui"]=> int(1) ["ajax"]=> int(1) ["return_format"]=> string(5) "value" ["placeholder"]=> string(0) "" } ["post_title"]=> string(9) "テスト" ["post_excerpt"]=> string(4) "test" ["post_status"]=> string(7) "publish" ["comment_status"]=> string(6) "closed" ["ping_status"]=> string(6) "closed" ["post_password"]=> string(0) "" ["post_name"]=> string(19) "field_*************" ["to_ping"]=> string(0) "" ["pinged"]=> string(0) "" ["post_modified"]=> string(19) "2020-**-** **:**:**" ["post_modified_gmt"]=> string(19) "2020-**-** **:**:**" ["post_content_filtered"]=> string(0) "" ["post_parent"]=> int(660) ["guid"]=> string(54) "https://www.example.com/?post_type=acf-field&p=661" ["menu_order"]=> int(0) ["post_type"]=> string(9) "acf-field" ["post_mime_type"]=> string(0) "" ["comment_count"]=> string(1) "0" ["filter"]=> string(3) "raw" }対応表
あまり使わないであろうものは省略。
$field = get_field_object()
→ $field = get_posts()
$field["ID"]
→ $field->ID
$field["key"]
→ $field->post_name
$field["label"]
→ $field->post_title
$field["name"]
→ $field->post_excerpt
$field["type"]
→ $field->post_content["type"]
$field["instructions"]
→ $field->post_content["instructions"]
$field["choices"]
→ $field->post_content["choices"]
$field["default_value"]
→ $field->post_content["default_value"]
$field["placeholder"]
→ $field->post_content["placeholder"]
$field["wrapper"]['id']
→ $field->post_content["wrapper"]['id']
フィールド名は
post_excerpt
、フィールドラベルはpost_title
、
その他のだいたいのデータはpost_content
内にシリアライズ化されて入っている模様。
最終的な実装
上記の表を踏まえて、
get_posts()
で取得したオブジェクトから適宜データを引っ張ってこればよい。
ただし注意点として、先にも書いたとおりpost_content
の中身はシリアライズ化されているため、unserialize()
するのを忘れないように注意。function.phpfunction add_custom_edit_post_search_form( $post_type ){ if ( $post_type === 'カスタム投稿タイプのスラッグ' ){ $selected = get_query_var('test') ? get_query_var('test') : ''; $args = array( 's' => 'test', //'name' => 'field_*************', あまり無いとは思うが、同じ名前のフィールドを複数使っている場合はフィールドキーで取得するほうが良い //'parent' => ***, もしくは、フィールドグループが親投稿として指定されているのでそれと組み合わせる 'post_type' => 'acf-field', ); $field_obj = get_posts( $args )[0]; $field_obj->post_content = unserialize($field_obj->post_content); printf('<select name="%s">', $field_obj->post_excerpt); printf('<option value="">すべての%s</option>', $field_obj->post_title); foreach($field_obj->post_content['choices'] as $value => $label){ $selected_text = ''; if($selected === $value) { $selected_text = ' selected'; } printf('<option value="%s"%s>%s</option>', $value, $selected_text, $label); } printf('</select>'); } }; add_action('restrict_manage_posts', 'add_custom_edit_post_search_form', 10, 1);
管理画面以外でも、たとえばサイドバー等に検索用のセレクトボックスを表示したいときなど、何かしらループ外で処理したいときに役に立つと思われる。
- 投稿日:2020-04-06T21:40:12+09:00
【PHPで解いてみた】2章 C++入門 AtCoder Programming Guide for beginners (APG4b)
2.01.ループの書き方と範囲for文
キーポイント
- パターンがあるプログラムを見つけたらループ文を使う。
- 配列の要素にはループを用いてアクセスする。
- ループ分の使い分け
- for文:カウンタによって繰り返す回数を管理したいとき
- while文:上記以外
気付き・学び
- 改行コードが含まれていることを念頭においてプラグラムを組む必要がある。
EX16 - 隣り合う同じ値を探す
<?php $Ai = trim(fgets(STDIN)); $Ai_list = explode(" ",$Ai); $flag = false; for($i=0;$i<count($Ai_list)-1;$i++){ if($Ai_list[$i] == $Ai_list[$i+1]){ echo "YES\n"; $flag = true; break; } } if(!$flag){ echo "NO\n"; }2.02.多重ループ
キーポイント
- 内側のループでbreakを書くと、内側のループは抜けれるが外側のループは抜けれない。
- 多重ループを1度に抜ける時はフラグ変数を用いる。
EX17 - 果物屋さんでお買い物
<?php fscanf(STDIN,"%d %d", $n,$s); $Ai_list = explode(" ",trim(fgets(STDIN))); $Pi_list = explode(" ",trim(fgets(STDIN))); $count = 0; for($i=0;$i<count($Ai_list);$i++){ for($j=0;$j<count($Pi_list);$j++){ if($Ai_list[$i]+$Pi_list[$j] == $s){ $count ++; } } } echo $count."\n";2.03.多次元配列
キーポイント
- 2次元配列は、2次元の表を扱うときに用いる。
- 2次元配列は、「1次元配列の配列」で表す。
- 2次元配列を構成する1次元配列の要素数が要素ごとに異なる配列をジャグ配列という。
- 多次元配列は、「N-1次元配列の配列」とかんがえる。
気付き・学び
- forを用いる時はインデックス変数の値とループ条件でミスが起こりやすいので注意する。
- 2次元配列を作成したいとき、2重配列内でfor文を用いるときはbreakを用いる。そうしなければ、2次元配列にはならない。
EX18.ゲーム大会
<?php fscanf(STDIN,"%d %d", $n,$m); $games = array(); if($m == 0){ echo "-"; }else { for ($t = 0; $t < $m; $t++) { fscanf(STDIN, "%d %d", $a, $b); $games[] = [$a, $b]; } for ($i = 1; $i <= $n; $i++) { for ($j = 1; $j <= $n; $j++) { for ($k = 0; $k < count($games); $k++) { if ($i == $games[$k][0] && $j == $games[$k][1]) { if ($j == $n) { echo "o"; } else { echo "o "; } break; } elseif ($i == $games[$k][1] && $j == $games[$k][0]) { if ($j == $n) { echo "x"; } else { echo "x "; } break; } elseif ($k == count($games) - 1) { if ($j == $n) { echo "-"; } else { echo "- "; } } } } echo "\n"; } }2.04.参照
キーポイント
- 参照渡しとは変数の参照先(メモリアドレス)をコピーすること。
- 参照は、関数で変数を複数返したいときに使える
気付き・学び
- 今回のEXでは、3つの変数情報を返したい関数だったが、参照を使うことでシンプルな関数で3つの変数を返せることを実感できた。
EX19 - 九九の採点
<?php $A_list = array(); for($i=0;$i<9;$i++){ $A_list[$i] = explode(" ",trim(fgets(STDIN))); } $correct_count = 0; $wrong_count = 0; function saiten(&$A,&$correct_count,&$wrong_count){ for($m=1;$m<=9;$m++){ for($n=1;$n<=9;$n++){ if($m*$n == $A[$m-1][$n-1]){ $correct_count++; }else{ $A[$m-1][$n-1]=$m*$n; $wrong_count++; } } } } saiten($A_list,$correct_count,$wrong_count); for($t=0;$t<9;$t++){ for($s=0;$s<9;$s++){ if($s==8){ echo $A_list[$t][$s]; }else{ echo $A_list[$t][$s]." "; } } echo "\n"; } echo $correct_count."\n"; echo $wrong_count."\n";2.05.再帰関数
キーポイント
- 「ある関数の中で同じ関数を呼び出す」ことを再帰呼び出しという。
- 再帰呼び出しを行わずに完了できる処理をベースケースといい、ベースケースが再帰の終了ポイント。
- 再帰呼出しを行い、その結果を用いて行う処理のことを再帰ステップという、このステップが再帰を起こす。
- 再帰関数のイメージは、再帰的に再起関数を呼び出して深くもぐり、下まで到達したのちに、返り値を複数回返すことで上っていくイメージ。
- 再起関数を作るとき、同じ意味を持つ関数を再起関数内で呼び出す。この時、呼び出した関数のアルゴリズムまで考えず、この関数はこういう処理をしてこの値を返してくれるんだと定義することでわかりやすく関数を作れる。
EX20 - 報告書の枚数
<?php function calc_report($par, $children, &$reports) { if(count($children[$par])==0){ return $reports[$par]=1; } $send_reports = 1; foreach($children[$par] as $child){ $send_reports += calc_report($child,$children,$reports); } return $reports[$par] = $send_reports; } function main() { fscanf(STDIN, "%d", $n); $p = explode(" ",trim(fgets(STDIN))); # 組織図を隣接リストで持つ $children; for ($i=0; $i<$n-1; $i++) { $parent = $p[$i]; $children[$parent][] = $i+1; } $reports = array(); # それぞれの組織が提出するレポートの数 for ($i=0; $i<$n; $i++) $reports[] = -1; calc_report(0, $children, $reports); # 0番目の組織から計算していく for ($i=0; $i<$n; $i++) { echo $reports[$i] . "\n"; } } main() ?>2.0.6 計算量
EX21は与えられたコードから1つの関数呼び出しをコメントアウトするだけなので、PHPへの書き換えは割愛します。
キーポイント
- コンピュータの記憶領域(メモリ)は有限であり、プログラムで変数を使用した分だけメモリを消費する
- プログラムの実行時間: 時間計算量 、メモリ使用量:空間計算量。これらは時間計算量はオーダーで表される。
- オーダー記法では O(N)と表されます。
- 投稿日:2020-04-06T21:13:23+09:00
PHPでMySQLと接続するために行ったこと
こんにちは、現在研修でPHP+DBでCRUD機能をつけた掲示板を作成しているのですがPHPとDBでうまく接続ができなかった為、共有したいと思います。なんかの参考になれば幸いです。
QそもそもなぜなかなかDB接続できなかったのか。
try{ $db = new PDO('mysql:dbname=php_db;host=localhost;charset=utf8', 'root', 'root'); } catch (PDOException $e) { echo 'DB接続エラー'. $e -> getMessage(); }上記のコードがMySQLと接続する際のコードになるのですが、PDOというPHPに接続するための関数の第一引数にデーターベース名とhost名、文字コードなどを指定でき、第二引数にユーザーの権限、第三引数にパスワードを入力しないといけないとのことですが、XAMPPを使用している方なら第三引数を特に指定せず ' ' だけでいいらしいのですがMAMP環境下だと第三引数にパスワードを指定しないといけなく、私は省略していいものだと思ってずっと接続できずにいました。アホでした。笑
Qどうやってテーブルにユーザー情報を格納したのか
これ、他のエンジニア様のコードや参考書に記載されているコード色々拝見したのですが、どれもわかりずらくそのままコピペしてもうまくいかなったんですよね。もしかしたら私のコードを他のエンジニア様がご覧になった際には怒られてしまうかもしれませんが一応うまくいったので共有します。
<?php session_start(); require('../db/connect.php'); //HTMLのコードが並ぶ $statement = $db->prepare('INSERT INTO users SET name=?, email=?, password=?'); $statement -> execute(array( $_POST['name'], $_POST['email'], password_hash($_POST['password'], PASSWORD_DEFAULT) )); ?>session_startと記載することでpostした情報を取得することができます。それを利用してテーブルに情報を格納していく形になります。
requireは先ほどのDB接続しているファイルを読み込んでいます。何回も同じ文を書きたくないですからね。
次にprepareと記載することでより安全にDBに情報を格納できるとのことだったので記載しております。引数にSQL文を記載していく形になります。INSERT INTOでユーザー情報を格納しています。
name = ?というのが下の文に書いてある$_POST['name']に入っている値が?の情報になります。
?に置き換わっているカラムの数だけ記載してあります。
最後にパスワードをハッシュ化してDBに格納します。最後に
自分はWeb系の企業に就職して初めてPHPを書いています。
PHPだけで掲示板サイト作るのとか勉強していないし無理ーーーー?とか思ってました。意外となんとかなっているというのが今の現状です。
今までは独学でRuby on Railsの勉強をしていました。
フレームワークってあんなにも便利だったんだなと改めて実感しています。
早く先輩方に追いつけるように日々頑張っていかないといけませんね。
- 投稿日:2020-04-06T20:15:02+09:00
【PHP8.0】throw文がthrow式になる
throw expressionというRFCが投票中です。
最初のアイデアは2019/12/06のSebastiaan Andewegによるツイート。
Really liking the arrow functions in PHP 7.4 so far! Unfortunately already found one missing feature: you can't throw Exceptions from them. The following results in a 'unexpected T_THROW' syntax error:
— Sebastiaan Andeweg (@sebandeweg) 2019年12月5日
fn() => throw new Exception('nope');
Would've cleaned up my test, but alas.それに対して2020/03/19にCarusoが反応し、そしてその日のうちにiluuu1994が最初のプルリクを出しました。
はえーよ。throw expression
Introduction
PHPのthrowは文であるため、アロー関数や三項演算子、NULL合体演算子などの式しか許されない場所から例外を投げることができません。
このRFCでは、それらを可能にするためthrow文を式に変更することを提案します。Proposal
式を記述可能なあらゆるコンテキストでthrowが利用可能になります。
以下は思いついた便利そうな例です。// アロー関数 $callable = fn() => throw new Exception(); // nullチェック $value = $nullableValue ?? throw new InvalidArgumentException(); // trueっぽいチェック $value = $falsableValue ?: throw new InvalidArgumentException(); // 空ではない配列チェック $value = !empty($array) ? reset($array) : throw new InvalidArgumentException();他にも、議論の余地のある使用方法があります。
このRFCでは、以下のような記述も許可されています。// ifを使った方が意図が明確になる $condition && throw new Exception(); $condition || throw new Exception(); $condition and throw new Exception(); $condition or throw new Exception();Operator precedence
throwが式になると、優先順位を決める必要があります。
以下は現時点で有効な書式の例です。throw $this->createNotFoundException(); // こうなる throw ($this->createNotFoundException()); // こうではない (throw $this)->createNotFoundException(); throw static::createNotFoundException(); // こうなる throw (static::createNotFoundException()); // こうではない (throw static)::createNotFoundException(); throw $userIsAuthorized ? new ForbiddenException() : new UnauthorizedException(); // こうなる throw ($userIsAuthorized ? new ForbiddenException() : new UnauthorizedException()); // こうではない (throw $userIsAuthorized) ? new ForbiddenException() : new UnauthorizedException(); throw $maybeNullException ?? new Exception(); // こうなる throw ($maybeNullException ?? new Exception()); // こうではない (throw $maybeNullException) ?? new Exception(); throw $exception = new Exception(); // こうなる throw ($exception = new Exception()); // こうではない (throw $exception) = new Exception(); throw $exception ??= new Exception(); // こうなる throw ($exception ??= new Exception()); // こうではない (throw $exception) ??= new Exception(); throw $condition1 && $condition2 ? new Exception1() : new Exception2(); // こうなる throw ($condition1 && $condition2 ? new Exception1() : new Exception2()); // こうではない (throw $condition1) && $condition2 ? new Exception1() : new Exception2();共通して言えるのは、全てがthrowキーワードより高い優先順位を持つということです。
このため、このRFCではthrowキーワードの優先順位を可能な限り低くすることを提案します。
現在有効なコードは、たとえ直感に反する動作だったとしても、今後も同じ動作をし続けます。
なぜなら、一般的にthrowは最後に使用するべき演算子であり、それ以降に記述した式は評価されないからです。低い優先順位の唯一の欠点は、短絡評価のために括弧が必須になることです。
$condition || throw new Exception('$condition must be truthy') && $condition2 || throw new Exception('$condition2 must be truthy'); // こうなる $condition || (throw new Exception('$condition must be truthy') && $condition2 || (throw new Exception('$condition2 must be truthy'))); // こうではない $condition || (throw new Exception('$condition must be truthy')) && $condition2 || (throw new Exception('$condition2 must be truthy'));もっとも、こんなコードはほぼ使われていないでしょう。
Backward Incompatible Changes
後方互換性のない変更はありません。
Other languages
C#では同じ文法が2017年に実装されました。
このような言語は他にはあまりありません。
ECMAScriptにプロポーザルがありますが、これは同じ問題を抱えているからです。Proposed PHP Version(s)
PHP8。
投票
投票は2020/04/19まで、2/3の賛成で受理されます。
2020/04/06時点では賛成14、反対1で、受理される見込みです。過去のML
9年前とか15年前にも同じ発想があったようですが、そのときは立ち消えになりました。
当時とはPHPのおかれた環境やRFCの出し方などがだいぶ異なることと、そしてなにより実物のプルリクがあるというのは大きいでしょう。感想
ややこしいよね文と式。
全てが式になればいいのに。というわけで、今後はもっと気軽にthrowすることができるようになります。
それどころかアロー関数で引数によって値を返したり例外Aを出したり例外Bを出したりすることもできちゃいますよ。
まあ正直、throwを出すようなややこしい式をアロー関数に書くんじゃないよと思ったりはするわけですが。
- 投稿日:2020-04-06T19:59:52+09:00
Laravel6系 Migration テーブルコメントをセットするには(MySQL, PostgreSQL)
ありそで無かったので記事にしますが、PostgreSQL未検証です。みたいに適当なので、参考程度にしてください。
もう書いちゃう方が早いので、以下コードを紹介します。
ProvidersへMigrateMacroProviderを作る
php artisan make:provider MigrateMacroProviderApp\Providers\MigrateMacroProvider::bootを編集
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Grammar; use Illuminate\Database\Schema\Grammars\MySqlGrammar; use Illuminate\Database\Schema\Grammars\PostgresGrammar; class MigrateMacroProvider extends ServiceProvider { /** * Register services. * * @return void */ public function register() { // } /** * Bootstrap services. * * @return void */ public function boot() { Blueprint::macro('comment', function($comment) { $this->addCommand('comment', [ 'table' => $this->getTable(), 'comment' => $comment, ]); Grammar::macro('compileComment', function( Blueprint $blueprint, $params ) { $compileComment = null; if (get_class($this) === MySqlGrammar::class) { $compileComment = "ALTER TABLE `{$params->table}` COMMENT '{$params->comment}'"; } elseif (get_class($this) === PostgresGrammar::class) { $compileComment = "COMMENT ON TABLE {$params->table} IS '{$params->comment}'"; } return $compileComment; }); }); } }config/app.phpへProvider追加
'providers' => [ // ... 略 App\Providers\MigrateMacroProvider::class, // ... 略 ],最後にShema::createの中でコメントセット
Schema::create('users', function (Blueprint $table) { $table->bigIncrements('id') ->comment('ID'); // ... 中略 // テーブル コメつけ $table->comment('ユーザテーブルでんねん'); }
$table->comment
使いたくない人は、適宜変えちゃってください。
- 投稿日:2020-04-06T19:59:52+09:00
Laravel6系 Migrationでテーブルコメつけたい(MySQL, PostgreSQL)
ありそで無かったので記事にしますが、PostgreSQL未検証です。みたいに適当なので、参考程度にしてください。
もう書いちゃう方が早いので、以下コードを紹介します。
いろいろ書き方あると思うので、こうやった方がもっと綺麗だよねーってあれば教えてくださいProvidersへMigrateMacroProviderを作る
php artisan make:provider MigrateMacroProviderApp\Providers\MigrateMacroProvider::bootを編集
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Grammar; use Illuminate\Database\Schema\Grammars\MySqlGrammar; use Illuminate\Database\Schema\Grammars\PostgresGrammar; class MigrateMacroProvider extends ServiceProvider { /** * Register services. * * @return void */ public function register() { // } /** * Bootstrap services. * * @return void */ public function boot() { Blueprint::macro('comment', function($comment) { $this->addCommand('comment', [ 'table' => $this->getTable(), 'comment' => $comment, ]); Grammar::macro('compileComment', function( Blueprint $blueprint, $params ) { $compileComment = null; if (get_class($this) === MySqlGrammar::class) { $compileComment = "ALTER TABLE `{$params->table}` COMMENT '{$params->comment}'"; } elseif (get_class($this) === PostgresGrammar::class) { $compileComment = "COMMENT ON TABLE {$params->table} IS '{$params->comment}'"; } return $compileComment; }); }); } }config/app.phpへProvider追加
'providers' => [ // ... 略 App\Providers\MigrateMacroProvider::class, // ... 略 ],最後にShema::createの中でコメつけ
Schema::create('users', function (Blueprint $table) { $table->bigIncrements('id') ->comment('ID'); // ... 中略 // テーブル コメつけ $table->comment('ユーザテーブルでんねん'); }
$table->comment
使いたくない人は、適宜変えちゃってください。
- 投稿日:2020-04-06T13:44:13+09:00
Laravel6.xでよく使うコマンド一覧 〜マイグレーションからモデルやコントローラ作成まで〜
個人的によく使うコマンドの使用例
コマンド一覧を見てもオプション関係がゴッチャになるので、定型文的によく使うLaravelのコマンドをメモ。
バージョンはLaravel 6.x です。最近リリースされた7では確認していませんが、確認が取れたらまた更新します。マイグレーション関連
Composerのオートローダを再生成
composer dump-autoloadシーダクラスを作成した後などは、Composerのオートローダを再生成するためにこちらのコマンドを実行する必要があります。
シーディングやマイグレーション時にデータベースのエラーが出るときは大体このコマンドを忘れているパターンが多いです。
上記コマンドを打たなくてもエラーにならない場合もあり、例えば元々あったシーダーを編集してシーディングし直すときは必要なさそうでした。マイグレーションファイルの作成
php artisan make:migration create_users_table --table=usersオプションを付加しながらマイグレーションファイルの作成。
--table=
の後にテーブル名を記載します。
コマンドを実行した日時によって、例えば下記のように、年_月_日_時間+指定した名前
といったファイル名でマイグレーションファイルは作成されます。
2020_02_19_155441_create_users_table.php
テーブル作成だけでなく、「カラムの修正」や「外部キーの設定」とか、操作したい内容によって
modify_colmn_type_user_id_on_users.php
やadd_foreign_keys_to_users_table
など、わかりやすい名前にすると良いかと思います。シーダーファイルの作成
php artisan make:seeder UsersTableSeederデータベースにテストデータを流し込むための入れ物を作ります。
今回の場合はUsersテーブル用のシーダーということになります。
テーブル名は各自で適切なものを設定してください。マイグレーションのやり直しと同時にシーディング
php artisan migrate:fresh --seed今あるテーブルを全ドロップしてテーブルを作り直し、DatabaseSeeder.phpに登録されたシーダーを同時に実行します。
php artisan migrate:refresh
などでエラーになったときは、このphp artisan migrate:fresh
で解消することもあります。シーディングのみ実行
php artisan db:seedマイグレーションとは別にDatabaseSeeder.phpに登録されたシーディングだけ実行したいときはこちらのコマンドを打ちます。
特定のシーダー、クラスのシーディングのみ実行
php artisan db:seed --class=UsersTableSeederDatabaseSeeder.phpに登録していないものや、登録はしたが個別にシーダーを実行したいという際に使用します。
--class=
の後にシーダーのクラス名を指定すればOKです。モデル・コントローラ関連
モデル作成
php artisan make:model Userモデルを作ります。通常Laravelでは、テーブル名は複数形、モデル名はテーブル名の単数形とします。
モデルと同時にコントローラ作成
php artisan make:controller UserControllerコントローラを作ります。こちらもテーブル名の単数形が良いです。
指定した名前でコントローラが作成され、今回の場合はUserController.php
というファイル名になります。php artisan make:controller UserController --model=Models/Userモデルも一緒に作成したい場合は後ろにオプションを付けて作成可能です。
--model=User
とすればappディレクトリ配下にUser.phpのモデル用ファイルができますが、モデル用のディレクトリを切って管理したい場合などは--model=Models/User
とすればapp/Modelsのディレクトリ配下にモデルが作成されます。php artisan make:controller UserController --resource自分でイチからfunctionを書かなくても、最初からCRUD処理用のメソッドをpublic functionとして最初に用意しておいてくれるのが上記のように
--resource
のオプションを付けた場合です。
これらは下記のように繋げて書くことで一度に実行できます。php artisan make:controller UserController --resource --model=Models/User
--resource
のオプションを付けた場合は、公式のドキュメントにあるように、下記のルートに紐づけられるメソッドが入れ物として用意されていますので、メソッドの内容は開発時に適宜上書きしていきます。動詞 | URI | アクション | ルート名 ------------------------------------------------------------------ GET | /photos | index | photos.index GET | /photos/create | create | photos.create POST | /photos | store | photos.store GET | /photos/{photo} | show | photos.show GET | /photos/{photo}/edit | edit | photos.edit PUT/PATCH | /photos/{photo} | update | photos.update DELETE | /photos/{photo} | destroy | photos.destroyこのリソースフルな状態でコントローラを作成すれば、下記のようにルートを定義すれば1行ですべてをまかなえます。
web.phpRoute::resource('photos', 'PhotoController');ただ自分はすべてのリソースを使う必要がなく、URL(URI)も
/photos/{photo}/edit
等から変更したいということも多く、割と従来通りに自分でルートの定義はしていたりします。今日はいったんそんな感じです。
参考URL
■マイグレーション -> Laravel 6.x データベース:マイグレーション
■シーディング -> Laravel 6.x データベース:シーディング
■モデル -> Laravel 6.x Eloquent:利用の開始
■コントローラ -> Laravel 6.x コントローラ
- 投稿日:2020-04-06T12:00:50+09:00
Laravelのroutingでprefixにパラメーターを入れる事ができる
LaravelでRestAPIライクなURLを設定する時には、prefix内にパラメーターを設定すると子のroute側でパラメーターが受け取れます。
例
/posts/{id}/comments
/posts/{id}/recommendsというようなAPIを作成するとします。
以下のようにしてRouteのところにidを入れてURLを指定することも出来ますが、
api.phpRoute::prefix('posts')->group(function () { Route::get('/{id}/comments', 'PostController@comments'); Route::get('/{id}/recommends', 'PostController@recommends'); });以下のようにprefixにパラメーターを入れる形でもControllerのメソッド側でidをパラメーターとして受け取ることが出来ます。
api.phpRoute::prefix('posts/{id}')->group(function () { Route::get('/comments', 'PostController@comments'); Route::get('/recommends', 'PostController@recommends'); });
- 投稿日:2020-04-06T09:44:10+09:00
Laravel環境構築(Mac)
はじめに
Laravelを初めて4月から初めて触る初学者です。
元はRailsを少し触っていましたが、色々あってLaravelを学習することにしたので、環境構築について自分なりにまとめておきます。PC情報
- macOS Catalina 10.15.3
- Macbook Pro (16-inch 2019)
構築手順
(1) Composer
composerのダウンロード
PHPのパッケージ管理システムであるComposerをインストールします。
上記リンクからDownload >> Manual Download(下の方)にアクセスし最新バージョンをダウンロードします。
(2020年4月時点では、1.10.1が最新のためこれをダウンロード)ダウンロードフォルダにcomposer.pharがダウンロードされたかと思います。
composerをいつても呼び出せるようにする
ターミナルを起動し、以下のコマンド入力
$ cd ~/Downloads$ sudo mv composer.phar /usr/local/bin/composerパスワードを聞かれるので、自分のOSログイン時のパスワードを入力します。
これでcomposerがbinファイルに移行され、いつでもcomposerが呼び出せるようになりました。実行権限の設定
このままだと、composerへのアクセス権が制限されている場合があるので、アクセス権限を変更します。
$ chmod a+x /usr/local/bin/composerComposerが実行可能か確認する
$ composer -vインストールしたバージョン(1.10.1)が表示されていれば完了です。
(2) Laravel Installerのインストール
Composerを用いたLaravel Installerのインストール
ターミナル上で以下のコマンドを実行します。
$ composer global require laravel/installer少し待つとインストールが完了します。
環境変数の追加
このままだと、LaravelのInstallerを実行するたびにファイルパスを指定する必要があります。
そのため、PATHを通しておきます。$ echo "export PATH=~/.composer/vendor/bin:$PATH" >> ~/.bash_profile $ source ~/.bash_profile(3) Laravelがインストールできてるか確認
testappの作成
1つプロジェクトを作ってみます。
ディレクトリをデスクトップに移動した上で、以下を実行します。$ laravel new testappこれでデスクトップ上にtestappというLaravelアプリケーションが作成されたと思います。
アプリケーションを実行してみる。
testappに移動して
$ cd testappサーバーを起動
$ php artisan serveこれで、localhost:8000にアクセスすると、LaravelアプリケーションのTOPページが表示されたと思います。
あとはアプリケーションを作っていくのみです!参考にしたサイトなど
- 投稿日:2020-04-06T08:59:13+09:00
Smartyを使ってみる
1 インストール
1 download
以下のサイトよりダウンロードします。
http://www.smarty.net/2 インストール
ダウンロードしたzipを解凍します。使うのはlibsフォルダのみです。
DocumentRootにsampleフォルダを作成しサンプルアプリを置くものとしてhtdocs + sample
└ libs <- ダウンロードしたsmartyのlibs※libsを複数プロジェクト共通で利用する場合は、
c:\smarty3\libs 等適当な場所へ配置しましょう。3 設定
smartyのlibsを共通で使う場合は、php.iniのinclude_pathの設定を行います。
";"を削除し、";c:\smarty3\libs"等と追記します。php.ini; UNIX: "/path1:/path2" ;include_path = ".:/php/includes" ↓ include_path = ".:/php/includes:/usr/local/lib/smarty3/libs" ; ; Windows: "\path1;\path2" ;include_path = ".;c:\php\includes" ↓ include_path = ".;c:\php\includes;c:\smarty3\libs"その他フォルダを作成します。
htdocs + sample
├ libs ← ダウンロードしたsmartyのlibs
├ templates ← 空のフォルダを作成
├ templates_c ← 空のフォルダを作成
├ cache ← 空のフォルダを作成
└ configs ← 空のフォルダを作成2 sampleアプリケーション作成
sampleフォルダにテンプレートとプログラムを作成します。
sample.tpl<html> <head> <title>Smarty Test</title> </head> <body> <h1>{$msg}</h2> </body> </html>sample.php<?php require_once('Smarty.class.php'); $smarty = new Smarty(); $smarty->template_dir = dirname(__FILE__).'/templates'; $smarty->compile_dir = dirname(__FILE__).'/templates_c'; $smarty->config_dir = dirname(__FILE__).'/configs'; $smarty->cache_dir = dirname(__FILE__).'/cache'; $smarty->assign('msg','Hello World!'); $smarty->display('sample.tpl'); ?>3 設定ファイルを使う(Smarty3)
プログラムから設定ファイルを読み込んでみます。
まずは、configs/sample.confを作り定義します。sample.conf# global url = "www.hoge.com" title = "Smarty" [mysql] DSN = "mysql:dbname=sample_db; host=localhost; charset=utf8" USER = "user1" PASS = "user1pass"そして、呼出側しは
sample.php: //設定ファイル読込み $smarty->configLoad('sample.conf'); //取得 $url = $smarty->getConfigVars('url'); //テスト表示 echo "url = ".$url."<br />"; //設定ファイル読込み(セクション) $smarty->configLoad('sample.conf', 'mysql'); //取得 $dsn = $smarty->getConfigVars('DSN'); //テスト表示 echo "dsn = ".$dsn."<br />"; : $smarty->display('sample.tpl');4 DBからの一覧表示(PDO)
DBのm_syainテーブルを全件取得し一覧表示してましょう。
まずは、DBまわり用のクラスCDbAccess.php<?php class DbAccess { private $pdo; /** * コンストラクタ * @param $smarty smartyオブジェクト */ public function __construct($smarty){ try { //configs/sample.confより設定取得 $smarty->configLoad('sample.conf', 'mysql'); $dsn = $smarty->getConfigVars('DSN'); $user = $smarty->getConfigVars('USER'); $pass = $smarty->getConfigVars('PASS'); //PDOインスタンス生成 $this->pdo = new PDO($dsn, $user, $pass); //接続語にオプション指定 //(エラー発生時に例外をスローする指定) $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch(PDOException $Exception) { self::abort('エラー:' . $Exception->getMessage()); } } /** * クローズ処理 */ public function close(){ try { $this->pdo = null; } catch(PDOException $Exception) { //エラーは無視 } } /** * テーブルデータ取得 * * @return テーブルデータ一覧 */ public function get_list(){ try { //m_syainテーブルより全件取得 $sql = "select * from m_syain where del_flg <> :delflg"; $stt = $this->pdo->prepare($sql); $stt->execute(array(':delflg'=>'1')); //$sql = "select * from m_syain where del_flg <> ?"; //$stt = $this->pdo->prepare($sql); //$stt->execute(array('1')); if ($list = $stt->fetchAll(PDO::FETCH_ASSOC) ){ return $list; } } catch(PDOException $Exception) { self::abort('エラー:' . $Exception->getMessage()); } } /** * フィールド名一覧取得 * * @return フィールド名一覧 */ public function get_keys(){ try { $sql = 'show columns from m_syain'; $stt = $this->pdo->prepare($sql); $stt->execute(); while ($column = $stt->fetch(PDO::FETCH_ASSOC) ){ $keys[] = $column['Field']; } } catch(PDOException $Exception) { self::abort('エラー:' . $Exception->getMessage()); } finally { return $keys; } } /** * die()文字化け対策 */ private function abort($status=0) { if (is_string($status) && !headers_sent()) { //ヘッダにcharset追加 header('Content-Type: text/plain; charset=UTF-8'); } die($status); } }呼出し側は
CDbAccess.php<?php : require_once ("CDbAccess.php"); : $smarty = new Smarty(); : $testdb = new DbAccess($smarty); $keys = $testdb->get_keys(); $list = $testdb->get_list(); $values = [ 'title' => 'test', 'head' => 'テスト', 'keys' => $keys, 'list' => $list ]; $smarty->assign ('values', $values); //データベース接続終了 $testdb->close(); : $smarty->display('sample.tpl'); ?>あとテンプレートは
template/sample.tpl: <h4>{$values.head}</h4> <table border=1 > {foreach $values.keys as $key}<th>{$key}</th>{/foreach} {foreach $values.list as $row} <tr>{foreach $row as $val}<td>{$val}</td>{/foreach}</tr> {/foreach} </table> :5 エラー情報出力
とりあえずphpファイルの先頭に以下の2行を入れる。
sample.php<?php ini_set('display_errors',1); //1:エラー表示、0:非表示 error_reporting(E_ALL); //エラー表示レベル(E_ALL & ~E_NOTICE等々指定) :6 デバッグ
<?php : //デバッグコンソールを出す $smarty->debugging = true; : //var_dumpで出力 var_dump($変数); :
- 投稿日:2020-04-06T06:43:02+09:00
PHP+αでTodoアプリを作る話②
前回
前回、PHPでTodoアプリを作るにあたって必要な環境設定を行ってまいりました。そちらをまだご覧になっていない方は是非そちらもご覧ください。
PHP+αでTodoアプリを作る話①
https://qiita.com/FangJoker7714/items/ed28eb66aa0947977f21はじめに
今回は基本的な機能の実装を行っていきたいと思います。具体的には、DBの設定、表示、検索、削除という流れになっております。
DBの設定
まずはデータベースの作成を行っていきたいと思います。前回の記事でphpMyadminにログインしたと思いますがそちらのほうに移動してください。
次に、データベースというボタンをクリックして下記の様な画面に移動してください。
筆者は既に入力していますが、データベースを作成するというフォームに好きなデータベース名を入力してください。照合順序とは何かを調べたところ、Microsoftの公式ドキュメントにこのように記されていました。
SQL Server の照合順序により、並べ替え規則、大文字と小文字の区別、およびアクセントの区別のプロパティをデータで利用できるようになります。
https://docs.microsoft.com/ja-jp/sql/relational-databases/collations/collation-and-unicode-support?view=sql-server-ver15Qiita内を調べたところ照合順序に関してわかりやすい記事を書いていた方がいらっしゃったのでそちらの記事も参考にされるとなお理解が深まるかと思います。
【MySQL】照合順序とは?
https://qiita.com/kazu56/items/6af85ffcf8d3954455ad長くなってしまいましたが、画面右側にデータベースが追加されたら成功です。
次にテーブルを作成していきたいと思います。先ほどの画面に下記の様に入力してください。
テーブル名は大文字と小文字を区別するものと区別しないものがあります。調べたところphpMyadmin(というかMySQL?)はこれらを区別しないという記事を発見しました。
[MySQL] テーブル名が小文字になってしまうので大文字にしたい
https://b.0218.jp/20121014022533.htmlカラム数とは列のことです。似たような言葉に行という言葉もありますが、両者の違いは下記のサイトがわかりやすく解説してくださっています。
データベースの用語を理解しよう 「テーブル」「レコード」「カラム」「フィールド」とは?
https://academy.gmocloud.com/know/20160425/2259先ほどの画面で実行をクリックすると下記の様な画面が表示されると思います。
どのような要素を使うかによってここはカスタマイズすればいいと思います。
次に具体的な値を入れていきます。詳しい説明はこの後致します。下記の様に編集してください。
何やらややこしそうですが関係するのは名前、データ型、長さ/値、インデックス、A_Iだけです。ひとつひとつ解説していきたいと思います。
まずは名前です。さっそく説明が難しいですが要は行の名前を付けるための項目です。今回は4種類あります。
・idとは作られたtodoの番号を割り振るための項目です。1番目に作られたならば1番、2番目なら2番といった要領です。
・todoとはどのような予定なのかを保存するための項目です。勉強をするとかギターを弾くといった具体的な予定を収めるための項目といった理解でいいと思います。
・createは作成された日時を割り振るための項目です。
※このような名前は一例で全てのDBでこのような名称及び機能で使用されるわけではありません。データ型とは、使う文字の範囲を決定するための項目です。整数だけなのか文字も使うのかといった感じです。(プログラミングをしていると時々見かけますね。)
・idは数値を扱うので数値型というものを使用します。今回はINTですが、もっと細かい値や大きな値を使用したいときはFLOAT等を使用するといいかと思います。
・todoは文字を扱うので文字型というものを使用します。今回はVARCHARを使用します。似たようなものにCHARというものも存在します。両者の違いを分かりやすく解説している方がいらっしゃったのでそちらの記事を参考にされるといいかと思います。またTEXTというものもありますが、より大きなデータを扱う際にはこちらの方がいいとのことでした。
・createは日時を扱うのでDATEを使用します。時間を扱いたい場合はTIME等を使用するといいかと思います。※下記サイトを参考にしました。
SQLのデータ型
http://park18.wakwak.com/~little-box/Dreamweaver/ultra405.htm「固定長カラム」と「可変長カラム」の違い
https://wa3.i-3-i.info/diff358db.html【SQL入門】CHARとVARCHARの違い
https://qiita.com/oseibo/items/c589430bdb00c6ab4922MySQL の VARCHAR と TEXT
http://3.1415.jp/ju5gxdka/長さ/値とは、文字数などを設定する際に使用します。
インデックスとは、todoの検索の際にこれを設定しておくと便利というものです。
A_Iとは、todoを作成したときに自動で順番を割り当てるというものです。長くなってしまいましたが設定はまだ終わっていません。前回作成したフォルダの中にtodo.phpというファイルを作成し下記の様に編集してください。
todo.php<?php $dsn = 'mysql:dbname = todo_app;host=localhost;charset=utf8mb4'; //DBの名前を定義 $user = 'root'; //ユーザー名 $password = 'xxxxx'; //パスワード try { //接続を試みるための処理 $dbh = new PDO($dsn, $user, $password); //データベースへ接続 } catch (PDOExeption $e) { //接続できない場合の処理 die('Connect Error: ' . $e->getCode()); //接続できなかった場合にはエラーを表示 } $dbh = null; //DBから切断 echo "hello world"; ?>小難しいコードが並んでいますね。コメントでも書いてありますが、補足を少々書き足しておきたいと思います。まず一番上はDBの名前を定義しています。よく見るとデータベース名と照合順序に設定したものに似た文字が並んでいるのがわかりますね。
次にユーザー名とパスワードを設定します。普段ログインに使用しているものを入力してください。
tryというところから何やらゴチャゴチャしていますね。
PDO(PHP Data Objects)とはPHPからSQLを操作するための標準のライブラリです。
(参考文献に記載)一番最後のechoは必ず必要というわけではないですが、成功しても何も表示されないよりかはいいかと思い書き足させていただきました。
少々長くなってしまいましたが、これでDBとPHPの紐づけは完了です。
おわりに
今回はPHPとDBの細かい設定を行ってまいりました。当初は一気に完成まで開設するつもりでしたが、思ったよりも内容が濃くなってしまったので本格実装は次回に回したいと思います。
- 投稿日:2020-04-06T03:00:55+09:00
phpで標準入力する方法
今回は備忘録をかねて、様々な例を使ってphpで行う標準入力について、まとめてみました。
基本編
app.php/* 1行のデータを取得する場合 * 例:「hogehoge」 */ $data = rtrim(fgets(STDIN)); /* ' '(空白行)で分けられた1行のデータを配列で取得する場合 * 例:「hogehoge gehogeho」 */ $data = explode(' ', rtrim(fgets(STDIN)));応用編
app.php/* 先頭の行に数字Nが書かれてて、後続のN行分のデータを配列で取得する場合 * 例:「2 * hogehoge * gehogeho」 */ $data = rtrim(fgets(STDIN)); $data_arr=array(); for($i=0;$i<$data;$i++){ $data_arr[]=rtrim(fgets(STDIN)); } /* 出力値 * Array( * [0] => gehogeho * [1] => hogehoge * ) */
- 投稿日:2020-04-06T00:31:53+09:00
Slackでモーダル(ダイアログ)を使ってデータを送信する
最近、弱々エンジニア会に参加しました。
記事貢献を兼ねて、久しぶりのQiita投稿です!
今回はSlackでSlackAppを使って、モーダル(ダイアログ)使ったデータの入出力を行う方法を解説していきたいと思います。今回作るもの
/modal というSlashコマンドを実行すると Block Kit Builder で作られたモーダルを表示して、入力された値をサーバーに送るSlackアプリを作ります。
参考資料
https://api.slack.com/surfaces/modals/using
開発環境
- PHP 7.2 (Laravel 6.16)
- GAE Standard (外部からアクセスできるサーバーであればなんでも可)
- Slack (フリープランでも可)
実装方法
Slackアプリの用意
モーダルを出す方法がメインなので細かくは書きません。
細かい部分は 運用中のブログ にまとめてるので気になる方はご覧ください〜アプリの作成
https://api.slack.com/apps から Create New App を選択して作っていきます。
Slashコマンドの登録
権限の設定
今回のサンプルであれば commands のみで作成できます。
※ Slashコマンドを登録すると勝手に入ると思います。
いけなかったら教えてください\(^o^)/interactivity の設定
ダイアログに値を入力された後、その値が送られる向き先の設定をする必要があります。
ワークスペースにインストール
Install App からインストールします。
表示に従っていればインストールできると思いますが、一点初回インストールの場合は下記のApp Display Name を設定しないと「インストールできるボットユーザーがありません」と怒られてしまいます。
以上でSlackの設定は終了です!(結構細かったかもしれん
コーディング
ようやく本編です。Laravelで書いていきます。
トークンとかは分けるべきなんですが、都合上まとめてますので良い感じで整理してください。/modal が実行されたときの処理
Route::post('/modal', 'SlackController@modal')->name('modal');な感じでSlackControllerにリクエストが飛んだ後の処理を書いていきます
SlackController.php(折り畳み)
<?php namespace App\Http\Controllers; use GuzzleHttp\Client; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\Log; class SlackController extends Controller { const BOT_TOKEN = 'xoxb-xxxxxxxx' /** * /modal のSlashコマンドを実行すると呼ばれる場所 * * @param Request $request * @return Response */ function modal (Request $request) { $url = 'https://slack.com/api/views.open'; $token = self::BOT_TOKEN; $view = $this->getModalContent(); $trigger_id = $request->input('trigger_id'); $params = [ 'view' => \GuzzleHttp\json_encode($view), 'trigger_id' => $trigger_id ]; $headers = [ 'Content-type' => 'application/json', 'Authorization' => 'Bearer ' . $token ]; $client = new Client(); $response = $client->request( 'POST', $url, // URLを設定 [ 'headers' => $headers, 'json' => $params ] // パラメーターがあれば設定 ); $log = \GuzzleHttp\json_decode($response->getBody()->getContents(), true); Log::info(print_r($log, true)); return response('',200); } /** * ダイアログのテンプレートを作る * * @return array */ function getModalContent () { return [ "type" => "modal", "title" => [ "type" => "plain_text", "text" => "メンバー登録", "emoji" => true ], "submit" => [ "type" => "plain_text", "text" => "登録", "emoji" => true ], "close" => [ "type" => "plain_text", "text" => "キャンセル", "emoji" => true ], "blocks" => [ [ "type" => "input", "block_id" => "name", "element" => [ "type" => "plain_text_input", "action_id" => "氏名", "placeholder" => [ "type" => "plain_text", "text" => "田中 太郎" ], ], "label" => [ "type" => "plain_text", "text" => "氏名" ] ], [ "type" => "input", "block_id" => "mail", "element" => [ "type" => "plain_text_input", "action_id" => "メールアドレス", "placeholder" => [ "type" => "plain_text", "text" => "xxx@gmail.com" ], ], "label" => [ "type" => "plain_text", "text" => "メールアドレス" ] ], [ "type" => "input", "block_id" => "language", "optional" => true, "element" => [ "type" => "checkboxes", "action_id" => "得意言語", "options" => [ [ "text" => [ "type" => "plain_text", "text" => "PHP", "emoji" => true ], "value" => "value-0" ], [ "text" => [ "type" => "plain_text", "text" => "Ruby", "emoji" => true ], "value" => "value-1" ], [ "text" => [ "type" => "plain_text", "text" => "Javascript/Node.js", "emoji" => true ], "value" => "value-2" ], [ "text" => [ "type" => "plain_text", "text" => "Python", "emoji" => true ], "value" => "value-3" ] ] ], "label" => [ "type" => "plain_text", "text" => "得意言語", "emoji" => true ] ] ] ]; }ちなみに以下のライブラリを使用しています。
GuzzleHttp
Google StackDriver Loggingメイン処理ですが Slack API の view.open というAPIを利用しています。
$url = 'https://slack.com/api/views.open';ダイアログの中身は Block Kit Builder で作ったものを使っていますが、細かい部分を修正しているので紹介します。
block_id と action_id は設定しておいたほうがいい
デフォルトでは付いていないのですが、 block_id と action_id はちゃんと設定したほうがいいです。
理由は次に紹介するパラメータの受け取り時にどの値なのかわからなくなりパラメータの取得が面倒になります。
それぞれ日本語も入れられたので結果を返す時にちゃっかり使っています。optional の場所を間違えないで
地味にやらかしたのが optional の場所。
optionalを付けることで任意の値として設定できるようになりますが、 element の中 に入れてしまっていたので何回か詰まっていました。正しくは element の外 です。正しく view.open が実行されていれば /modal を実行した時にダイアログは表示されるようになっているはずです。
モーダル経由で入力値を送られてきたときの処理
Route::post('/interactiveMessage', 'SlackController@interactiveMessage')->name('interactiveMessage');な感じでSlackControllerにリクエストが飛んだ後の処理を書いていきます
SlackController.php(折り畳み)
<?php namespace App\Http\Controllers; use GuzzleHttp\Client; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\Log; class SlackController extends Controller { const BOT_TOKEN = 'xoxb-xxxxxxxx'; // 先程のControllerに追記する形で /** * モーダルから送られてくるリクエストを受け取る * * @param Request $request * @return Response */ function interactiveMessage (Request $request) { // 早期レスポンス response('',200)->send(); $payload = $request->input('payload'); $postData = \GuzzleHttp\json_decode($payload, true); Log::info(print_r($postData, true)); switch ($postData['type']) { case 'view_submission': // submit された時 /* * なんらかの処理 */ $text = '登録しました!'; $user = $postData['user']['id']; $attachments = $this->makeAttachments($postData['view']['state']['values']); Log::info(print_r($attachments, true)); // メッセージを送る // modalを使う場合、response_urlを取得できなかったので強制的にメッセージ投稿 $response = $this->postEphemeral($user, $text, $attachments); $log = \GuzzleHttp\json_decode($response->getBody()->getContents(), true); Log::info(print_r($log, true)); break; case 'block_actions': // チェックボックスなどが更新された時 break; } } /** * 「あなただけに表示されています」のメッセージを送る * * @param $user * @param $text * @param $attachments * @return mixed */ function postEphemeral ($user, $text, $attachments = []) { $url = 'https://slack.com/api/chat.postEphemeral'; $token = self::BOT_TOKEN; $params = [ 'token' => $token, 'attachments' => \GuzzleHttp\json_encode($attachments), 'channel' => $user, 'text' => $text, 'user' => $user, ]; Log::info(print_r($params, true)); $client = new Client(); $response = $client->request( 'POST', $url, ['query' => $params] ); return $response; } /** * 回答を受け取った結果をまとめる * * @param $values * @return array */ function makeAttachments ($values) { $text = ""; foreach ($values as $key => $value) { foreach ($value as $name => $detail) { switch ($detail['type']) { case 'plain_text_input': $text .= $name . ' : ' . $detail['value'] . "\n"; break; case 'checkboxes': $checkedValue = ''; if (isset($detail['selected_options'])) { foreach ($detail['selected_options'] as $selected_option) { $checkedValue .= $selected_option['text']['text'] . " "; } } else { $checkedValue .= '指定なし'; } $text .= $name . ' : ' . $checkedValue . "\n"; break; } } } $attachments[] = [ 'text' => $text, ]; return $attachments; }こちらも同様に注意すべきポイントを紹介します。
早期レスポンスをしておいたほうが良い
// 早期レスポンス response('',200)->send();モーダルからの値を送った後、3秒以内にレスポンスを返さないとエラーが返る仕様になっています。
DB更新処理など少し時間がかかる処理を挟む場合、早期レスポンスでまずダイアログを閉じてしまってから処理を行いましょう。モーダルは色々なtypeで送られてくるのでtypeによる振り分けが必要
モーダルで「登録」を押した時、
type = 'view_submission'
でリクエストが飛びます。
しかし、それ以外にもセレクトボックスを更新した場合もtype = 'block_actions'
でリクエストが飛びます。
https://api.slack.com/surfaces/modals/using#response_actionsそのため、typeによってうまくハンドリングしないとエラーになってしまうのでそこんとこうまく書きましょう。
モーダルの処理が完了した後は完了メッセージはない?
分からなかった点ですが、普通であれば response_url が返ってくるのですが、モーダルの場合は response_url が返ってこなかったので完了メッセージを送ることが出来ませんでした(詳しい方教えて下さい)
完了メッセージがないのは気持ち悪いので、今回は chat.postEphemeral を使ってレスポンスを返しました。$url = 'https://slack.com/api/chat.postEphemeral';積極的に綺麗なモーダルを作って行こうな
以前は dialog.open というAPIを使ってダイアログを表示するのが主流でしたが、新しい機能として modal が作られ、まだまだ参考資料が少ない状態です。
複数モーダルに渡るパラメータの入力なども可能なので、今回の記事は本当に基礎の基礎のような記事です。
この記事を参考にもっと色々な使い方が増えていってほしいと思います。Twitter もやっているので、なにかありましたらフォロー&アドバイス頂けると嬉しいです
ここまで読んで頂きありがとうございました〜〜
- 投稿日:2020-04-06T00:01:48+09:00
laravel入門2版(本)が全然始められない人のためのまとめ
吐きそうになりながらlaravelインストールした話
ララベルの代表書であるララベル入門第二版を購入してインストールしようとしたがつまずくポイントが多すぎて吐きそうになったので過去の自分を助けてあげるつもりで解決策を書いていこうと思います。
環境/対象者
mac Os catalina
これからlaravel6とphp7.2を使おうとしている人
terminalのコマンドがある程度(directoryの移動程度)ができる人詰みポイントのまとめ
- まずこれが設定できていないと詰む zshとbash
- composerでlaravelをインストールしようとしてもphpのバージョンがふるかったり相性が合わないと詰む
- pathの通しかたが分からなくて詰む、というかパスが何か分からなくて詰む
ますこれができていないと詰むzashとbash
まずlaravelを初めてインストールしようとした時にterminalのshellがzshだとパスがうまく通せなくて詰みます。
bashに変更しましょう。echo $SHELLでshellがbashになっているか確認しましょう。
bin/bashの表示が出ればbashになっているのでOKです。
もしbashになっていなかったらshellスクリプトの変えかた でググればすぐでてくるので検索してみてください。composerでlaravelをインストールしようとしてもphpのバージョンがふるかったり相性が合わないと詰む
さてまずはcomposerのインストールの方法ですが
composer公式サイト(getcomposer.org)でcomposer.pharをダウンロードしてきてください。
ダウンロードした後は、composer.pharをsudo mv composer.phar /usr/local/bin/composerでbinディレクトリーに移動させた後に
chamod a+x /usr/local/bin/composerでパーミッションを変更してあげてください。
ここまできたらcomposer global require laravel/installerでララベルのインストールをすることになるのですが、おそらくできないと思います。
- 原因
Installation request for laravel/installer ^2.1 -> satisfiable by laravel/installer[v2.1.0]. laravel/installer v2.1.0 requires ext-zip * -> the requested PHP extension zip is missing from your system.多分こんなかんじのエラーが出てくると思いますが、ざっくり説明すると、ext-zipっていうphpのファイルがないよっていってます。
- 解決策
調べていたら、yumでphpのたりないファイルをインストールする方法が書いていたのですが、macでyumを使おうとするとめんどくさそうだったのでhomebrewを使って、ext-zipをインストールします。
brew update brew install php@7.4 brew link php@7.4上のコードを打つとphpのファイルがインストールされるのでひとまずララベル自体をインストールすることはできるます。
pathの通しかたが分からなくて詰む、というかパスが何か分からなくて詰む
最後に pathの通しかたが分からなくて詰む、というかパスが何か分からなくて詰む、ですが、
ますはpathとは何かというのをざっくり説明します。pathを通すというのは、terminal上で「laravel」という文言を使えるようにすることです。
pathが通っていないとlaravel:command not foundとでてきてコマンド使えなくて作業できません。
pathは
.bash_profileというファイルに直接書き込まなければいけないので、ますはホームディレクトリに移動してください。その後に$ ls -a $ cd .bash_profileで隠しファイルの.bash_profileが存在することを確認してから.bash_profileに移動してください。
次に$ vim ~/.bash_profileでvimを起動して.bash_profileを編集していきます。
vimの操作は他にいい記事がたくさんあるので検索してください。
vimで開いた後は.bash_profileの最後尾にexport PATH=$HOME/.composer/vendor/bin:$PATHを追加して保存した上で終了してください。
最後に$ source ~/.bash_profileを入力することで変更を保存します。
$ laravel -Vでlaravelコマンドが使えることを確認すればひとまず完了です。
まとめ
本を買ったはいいけどlaravelがインストールできなくてくるしんでる人の助けになれば嬉しいです。
他に不明点や間違いがあればコメントにてご指摘お願いします。