20210329のPHPに関する記事は16件です。

生PHPについて知識を資産化していく①

前提として

  • 自分が勉強してきた知識を保存するために書いています。
  • 本当に初歩的な事から書いていってます。
  • 万が一間違っている可能性もございますので、その時はご指摘頂ければ嬉しいです。
  • 常時更新していく予定です。
  • アウトプット用ですので、MAMPを使用していきます。

ブラウザに「test」表示させる

index.php
<?php

echo 'test';

?>

現在のPHPのバージョンと設定一覧を表示させる

index.php
<?php

phpinfo();

?>

数字と文字

  • 数字は半角で入力する
  • 文字はシングル(またはダブル)クウォーテーションをつける
index.php
<?php

echo 123; → 数字

echo('こんにちは') //文字
echo("こんにちは") //文字

?>

変数

「$」マークで書き始める

index.php
<?php

$test = 123; //「123」という数字が$testに代入される
$tsuishi = 'こんにちは'; //「こんにちは」という文字が$tsuishiに代入される

echo $test; //ブラウザに 123 と表示される
echo $tsuishi; //ブラウザに こんにちは と表示される

?>

※同じ変数は後から書いたものが上書きされる

index.php
<?php

$test = 123;
$test = 456;

echo $test; //ブラウザに 456 と表示される

?>

型を確認する

var_dump()

index.php
<?php

$test = 123;
$tsuishi = 'こんにちは';

var_dump($tsuishi); //$tsuishiの型を確認する → string(15)と表示される
var_dump($test); //$testの型を確認する → int(123)と表示される

?>

定数

変わらない文字、数
同じ定数があっても上書きされない。

index.php
<?php

const MAX = 10;
const MAX = 20;

echo MAX; //10と表示

?>

配列(1行バージョン)

※プログラミングは0から始まるので、

  • 0の場合1
  • 1の場合2
  • 2の場合3
index.php
<?php

$array = [1, 2, 3];

echo $array[1]; //2と表示

//中身を全部みたい場合
echo '<pre>'; //改行して表示させる
var_dump($array);
echo '</pre>';



array(3) {
  [0]=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  int(3)
}


?>

配列(2行3列バージョン)

index.php
<?php

$array_2 = [
  ['赤', '青', '黄'],
  ['緑', '紫', '黒']
];

echo '<pre>';
var_dump($array_2);
echo '</pre>';



array(2) { //[0],[1]の2つの配列がある
  [0]=>
  array(3) { //[0]の中に配列が3つある
    [0]=>
    string(3) "赤"
    [1]=>
    string(3) "青"
    [2]=>
    string(3) "黄"
  }
  [1]=>
  array(3) { //[1]の中に配列が3つある
    [0]=>
    string(3) "緑"
    [1]=>
    string(3) "紫"
    [2]=>
    string(3) "黒"
  }
}

// 紫を出したい場合
echo $array_2[1][1]; //[1]の配列の中の[1]の紫を出力する

?>

連想配列

配列と書き方が違う。

配列

数字(順番固定)と値がセット。
$array[1];

連想配列

キーと値がセット。
キー => 値
key => value
$array["key"];

index.php
<?php

$array_member = [
  'name' => '本田',
  'height' => 170,
  'hobby' => 'サッカー'
];

echo $array_member['hobby']; //サッカーと表示

echo '<pre>';
var_dump($array_member);
echo '</pre>';



array(3) {
  ["name"]=>
  string(6) "本田"
  ["height"]=>
  int(170)
  ["hobby"]=>
  string(12) "サッカー"
}

?>

※(例)学校に例えて考えてみる
2組それぞれの生徒の特徴を記述する

index.php
<?php

$array_member_2 = [
  '本田' => [
    'height' => 170,
    'hobby' => 'サッカー'
  ],
  '香川' => [
    'height' => 165,
    'hobby' => 'サッカー'
  ]
];

//香川の身長を出したい時
echo $array_member_2['香川']['height']; //165と表示

?>

各組それぞれの生徒の特徴を記述する

index.php
<?php

$array_member_3 = [
  '2組' => [
    '本田' => [
      'height' => 170,
      'hobby' => 'サッカー'
    ],
    '香川' => [
      'height' => 165,
      'hobby' => 'サッカー'
    ]
  ],
  '3組' => [
    '長友' => [
      'height' => 160,
      'hobby' => 'サッカー'
    ],
    '乾' => [
      'height' => 168,
      'hobby' => 'サッカー'
    ]
  ]
];

// 3組の長友のheightを出したい時
echo $array_member_3['2組']['長友']['height']; //160と表示

?>

このように段階で分けていくことにより、
クラス・組・学年・学校・都道府県などで分けてデータを引っ張ってこれる

演算子(計算や判定)抜粋

四則演算子 (+, -, *, /, %)

比較演算子 (>, >=, +=, ===, !==)

論理演算子 (and, &&, or, ||, xor)

!= nullや、!= emptyはよく使う
== は使わない === を使う

//== → 一致
//=== → 型も一致

++ でインクリメント (1ずつ増える)

index.php
<?php

$test_1 = 7;
$test_2 = 3;

$test_3 = $test_1 + $test_2;
$test_4 = $test_1 - $test_2;
$test_5 = $test_1 * $test_2;
$test_6 = $test_1 / $test_2;
$test_7 = $test_1 % $test_2;

echo $test_3; //4と表示
echo $test_4; //4と表示
echo $test_5; //21と表示
echo $test_6; //2.33333333...と表示
echo $test_7; //1と表示

?>

条件分岐

プログラムが得意なもの
条件分岐(信号機など)と繰り返し

if文

index.php
<?php

if (条件) {
  条件が真なら実行
}

//例
$height = 90;

if ($height == 90) {
  echo '身長は' . $height . 'cmです'; //身長は90cmです と表示
}

?>

else文

index.php
<?php

if (条件) {
  条件が真なら実行
} else {
  条件が偽なら実行
}

//例
$height = 90;

if ($height === 95) {
  echo '身長は' . $height . 'cmです';
} else {
  echo '身長は' . $height . 'cmではありません'; //身長は90cmではありません と表示
}


?>

elseif文

index.php
<?php

if (条件) {
  条件が真なら実行
} elseif{
  上記条件が偽でこちらが真なら実行
} else {
  条件が偽なら実行
}

//例
$signal = 'yellow';

if ($signal === 'red') {
  echo '止まれ';
} elseif ($signal === 'yellow') {
  echo '一旦停止';
} else {
  echo '進め';
}
//一旦停止 と表示

?>

if文の中にif文

index.php
<?php
//例
$speed = 80;

if ($signal === 'blue') {
  if ($speed >= 80) {
    echo 'スピード違反'; //スピード違反 と表示
  }
}

?>

〜ではない(!==)

!= → ~ではない
!== → 型が同じかどうかも含め~ではない

//例

index.php
<?php

$height = 91;

if ($height !== 90) {
  echo '身長は90cmではありません。'; //身長は90cmではありません。 と表示
}

?>

データが入っているかどうか

※empty → データが空かどうか確認する

index.php
<?php

//例
$test = '';

if (empty($test)) {
  echo '変数は空です'; //変数は空です と表示
}

//例
$test = '1'; //文字

if (!empty($test)) {
  echo '変数は空ではありません'; //変数は空ではありません と表示
}

//例
$test = '1';

if (empty($test)) {
  echo '変数は空です';
} else {
  echo '変数は空ではありません'; //変数は空ではありません と表示
}

elseを使うとスマートではないのでif (!empty($test))の書き方が良い

?>

AND(&&) と OR(||)

index.php
<?php

//例 AND(&&)
$signal_1 = 'red';
$signal_2 = 'blue';

if ($signal_1 === 'red' && $signal_2 === 'blue') {
  echo '赤と青です'; //赤と青です と表示
}

//例 OR(||)
$signal_1 = 'red';
$signal_2 = 'yellow';

if ($signal_1 === 'red' || $signal_2 === 'blue') {
  echo '赤です'; //赤です と表示
}

?>

三項演算子

条件 ? 真 : 偽

index.php
<?php

//例
$math = 80;

$comment = $math > 80 ? 'good' : 'not good';

echo $comment; //not good と表示
?>

foreach

//複数の値
foreach(複数形 as 単数形)

index.php
<?php
//例
$members = [
  'name' => '本田',
  'height' => 170,
  'hobby' => 'サッカー'
];

//valueのみ表示
foreach($members as $member) {
  echo $member; //本田170サッカー と表示
}

//keyとvalueそれぞれ表示
foreach($members as $key => $value) {
  echo $key . 'は' . $value . 'です';
}
//nameは本田ですheightは170ですhobbyはサッカーです と表示

?>

多段階の配列を展開

index.php
<?php
//例
$members_2 = [
  '本田' => [
    'height' => 170,
    'hobby' => 'サッカー'
  ],
  '香川' => [
    'height' => 165,
    'hobby' => 'サッカー'
  ],
];

foreach($members_2 as $member_1) {
  foreach($member_1 as $member => $value) {
    echo $member . 'は' . $value . 'です';
  }
}

//heightは170ですhobbyはサッカーですheightは165ですhobbyはサッカーです と表示

?>

繰り返し文

for, while

for文()

繰り返す数が決まっている
//処理の例

for($i = 0, $i < 10; $i++) {
  処理
}
index.php
<?php
//例
for($i = 0; $i < 10; $i++) {
  echo $i;
}
//0123456789 と表示

?>
index.php
<?php
//break
//例(5になったら止める)

for($i = 0; $i < 10; $i++) {

  if ($i === 5) {
    break;
  }
  echo $i;
}
//01234 と表示

?>
index.php
<?php
//continue
//例(5をスキップする)

for($i = 0; $i < 10; $i++) {

  if ($i === 5) {
    continue;
  }
  echo $i;
}
//01234 と表示

?>

while文

繰り返す数が決まっていない

index.php
<?php

$j = 0;
while($j < 5) {
  echo $j;
  $j++;
}
//01234 と表示

?>

switch文

  • if文の方が見やすいので基本はif文を使うようにする
  • caseの後にbreakを入れないとそのまま処理が走ってしまうので入れるようにする
  • どれにも当てはまらない場合は default となる
index.php
<?php

$data = 1;

switch($data) {
  case 1:
    echo '1です';
    break;
  case 2:
    echo '2です';
    break;
  case 3:
    echo '3です';
    break;
  default:
  echo '1〜3ではありません';
}

//1です と表示

?>

関数

関数は2種類

  • 組み込み関数 → 準備してある関数
  • ユーザー定義関数 → 自由に作れる関数

組み込み関数に関してはこちら
関数リファレンス

function test(引数) {
  //処理

  return 戻り値;
}
index.php
<?php
//インプット引数 なし
//アウトプット戻り値 なし

function test() {
  echo 'テスト';
}

test();
//テスト と表示

?>
index.php
<?php
//インプット引数 あり
//アウトプット戻り値 なし

function getComment($string) {
  echo $string;
}

getComment('コメント');
//コメント と表示

?>
index.php
<?php
//インプット引数 なし
//アウトプット戻り値 あり

function getNumberOfComment() {
  return 5;
}

var_dump(getNumberOfComment());
//int(5) と表示

$commentNumber = getNumberOfComment();

echo $commentNumber;
//5 と表示

?>
index.php
<?php
//引数 2つ
//アウトプット戻り値 あり

function sumPrice($int1, $int2) {
  $int3 = $int1 + $int2;
  return $int3;
}

$total = sumPrice(3, 5);

echo $total;
//8 と表示

?>

文字列関数

index.php
<?php
//文字列の長さ

$text = 'abc';

echo strlen($text);
//3 と表示

?>
index.php
<?php
//日本語 SJIS, UTF-8 3〜6バイト(文字1文字で3バイト) → strlen

$text = 'あいうえお';

echo strlen($text);
//15 と表示

?>
index.php
<?php
//マルチバイト → 単純に日本語の文字数(mb_strlen)

$text = 'あいうえお';

echo mb_strlen($text);
//5 と表示

?>
index.php
<?php
//文字列の置換 → str_replace()

$str = '文字列を置換します';

echo str_replace('置換', 'ちかん', $str);
//文字列をちかんします と表示

?>
index.php
<?php
//指定文字列で分割 → explode()

$str_2 = '指定文字列で、分割します';

echo '<pre>';
var_dump(explode('、', $str_2)); //「、」で分割する
echo '</pre>';



array(2) {
  [0]=>
  string(18) "指定文字列で"
  [1]=>
  string(15) "分割します"
}

?>
index.php
<?php
//正規表現 → preg_match()
//文字列かどうか, 数字かどうか, 郵便番号, メールアドレスかどうかを確認する

preg_match('/文字列/, $str_3');

?>

詳しくはこちらをご確認ください
正規表現パターンに使用可能な修飾子
webdesign leaves

index.php
<?php
//指定文字列から文字列を取得する → substr()
//文字化けしないように取得する → mb_substr()

echo substr('abcde', 1);
//bcde と表示

echo mb_substr('あいう', 2);
//う と表示

?>

配列の関数(array)

index.php
<?php
//配列に配列を追加する(array_push)

$array = ['りんご', 'みかん'];

array_push($array, 'ぶどう', 'なし');

echo '<pre>';
var_dump($array);
echo '</pre>';



array(4) {
  [0]=>
  string(9) "りんご"
  [1]=>
  string(9) "みかん"
  [2]=>
  string(9) "ぶどう"
  [3]=>
  string(6) "なし"
}
//ぶどうとなしが追加される

?>

PHPの配列関数に関してはこちら
図解でわかるPHP配列関数一覧

関数名について

キャメルケースかスネークケースで関数名をつける。

//キャメルケース
checkPostalCode() → 最初の英単語の頭文字は小文字、次の英単語から頭文字は大文字で書く

//スネークケース
check_postal_code() → 英単語ごとにアンダーバーで区切る

※基本的にはスッキリしているキャメルケースで書く!!

組み込み関数

<?php
//郵便番号のチェック

$postalCode = '123-4567';

function checkPostalCode($str) {
  // ハイフンをなくすために空にする
  $replaced = str_replace('-', '', $str);
  // ハイフンがなくなった後の文字の数を取る
  $length = strlen($replaced);

  var_dump($length);  //int(7) と表示

  if ($length === 7) {
    return true;
  }
  return false;
}

var_dump(checkPostalCode($postalCode));
// bool(true) と表示

?>

変数のスコープ

有効範囲
例:
+ グローバル = 全世界
+ ローカル = それぞれ

index.php
<?php

$globalValiable = 'グローバル変数です';

function checkScope() {
  $localValiable = 'ローカル変数です';
  echo $localValiable;
  (関数の中で$globalValiableを指定しても使えない)

 //どうしても関数の外にあるグローバル変数を使う場合は、globalと前につける
 global $globalValiable;
  echo $globalValiable; //グローバル変数です と表示

}

echo $globalValiable; //グローバル変数です と表示
echo $localValiable; //表示されない

checkScope(); //ローカル変数です と表示

?>

※globalというキーワードを使うことはほとんどない

グローバル変数を使いたい場合は、インプットを引数に書くようにするとスマートに書ける

index.php
<?php

$globalValiable = 'グローバル変数です';

function checkScope($str) {
  $localValiable = 'ローカル変数です';
  echo $str;
}

echo $globalValiable;
echo $localValiable;

checkScope($globalValiable); //グローバル変数です と表示

?>

ファイルの読み込み

require(); require_once(); //エラー

include(); include_once(); //警告
commom.php
<?php

$commonVariable = "共通の変数です";

function commonTest() {
  echo '外部ファイルの関数です';
}
?>
<?php

require 'common.php';

echo $commonVariable; //共通の変数です と表示

commonTest(); //外部ファイルの関数です と表示

?>

マジック定数

  • __DIR__ ← 現在のファイルの絶対パスを表示する
  • __FILE__ ←現在のファイルを表す
index.php
<?php

__DIR__; //例:/Applications/MAMP/htdocs/***/ の中にファイルがあると判断できる

__FILE__; //例:/Applications/MAMP/htdocs/***/index.php のファイルだと判断できる

?>

次回へつづく(執筆中)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHP】確認画面で画像を保存せず表示

はじめに

ユーザー新規登録で確認画面に遷移した際に、戻るボタンを押すと画像だけが残ってしまう問題が発生しました。
confirm_disp_before.gif
こちらは確認画面で画像をフォルダに保存していたため、起きてしまいました。
confirm_disp_after.gif
そこで確認画面で画像を一時的にセッションに保存し、登録確定された場合のみ保存される上記動作画面のような処理を実装します。

実装方法

画像情報をセッションに保存

user_add.php
<h2>新規登録</h2>
<form method="post" action="user_add_check.php" enctype="multipart/form-data">
<input type="text" name="name" class="user_name_input" placeholder="ユーザー名">
<input type="password" name="pass" class="user_pass_input" placeholder="パスワード">
<input type="file" name="image" id="my_image" accept="image/*" multiple>
<input class="btn btn-outline-info" type="button" onclick="history.back()" value="戻る">
<input class="btn btn-outline-dark" type="submit" value="登録">
</form>

今回は、このような登録画面で実装します。

user_add_check.php
<?php
session_start();
$_SESSION['image']['data'] = file_get_contents($_FILES['image']['tmp_name']);
$_SESSION['image']['type'] = exif_imagetype($_FILES['image']['tmp_name']);
?>
<img src="image.php">
<form method="post" action="user_add_done.php">
<input type="hidden" name="name" value="<?= $user_name ?>">
<input type="hidden" name="pass" value="<?= $user_pass ?>">
<input type="hidden" name="image_name" value="<?= $user_image['name'] ?>">
<input type="button" class="btn btn-outline-info modal_close" onclick="history.back()" value="戻る">
<input type="submit" class="btn btn-outline-dark" value="OK">
</form>

確認画面で画像を出力するため、一時的にセッションへ保存して表示します。

<?php
session_start();
$_SESSION['image']['data'] = file_get_contents($_FILES['image']['tmp_name']);
$_SESSION['image']['tmp'] = exif_imagetype($_FILES['image']['tmp_name']);
?>
<img src="image.php">

確認画面での表示のため$_SESSION['image']['data']に画像データを渡します。
$_SESSION['image']['tmp']は後ほど、確認画面で画像を表示の際にmimeタイプ取得のため用います。

セッションに保存した情報を元に、画像を表示するファイルを作成します。

image.php
<?php
session_start();
switch ($_SESSION['image']['type']) {
    case IMAGETYPE_JPEG:
    header('content-type: image/jpeg');
    break;
    case IMAGETYPE_PNG:
    header('content-type: image/png');
    break;
    case IMAGETYPE_GIF:
    header('content-type: image/gif');
    break;
    }
echo $_SESSION['image']['data'];
?>

セッションから画像情報を出力します。

これで画像を確認画面で表示することができ、
戻るボタンを押しても保存される問題はございません。

user_add_done.php
$user_image = $_POST['image_name'];
file_put_contents('./image/'.$user_image,$_SESSION['image']);

最後に登録を確定した場合に、画像を指定のファイルに保存するようにします。

参考URL

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelのルーティングの状態をファイルで確認してみる

環境

バージョン等
Laravel 6.20.19
MacOS 10.15.7 Catarina

①普通にルーティングを確認してみる

Laravelの通常のWebページとしてのルーティングはRoutes/web.phpにいろいろと記載していますが、
prefixとかgroupeなどを記述していると、実際にどうなっているか分からなくなったりしますよね。

これを確認する時に使うのが、

ターミナル
$ php artisan route:list

というコマンド。

なのですが、これがずら〜と表示されてちょっと分かりづらい。

Before

image.png

②見やすくルーティングを表示する

ということでこれをファイルとして保存して見やすく表示してみましょう。
route:listコマンドに> .routeを追加すればOKです。

ターミナル
$ php artisan route:list > .route

これでプロジェクトの直下に.routeというファイルが保存されました。

After

中身を表示するとこんな感じで見やすく表示されます。
image.png
作成してしまえばその後はコマンドを実行せずに確認できるのも嬉しいですね。
なおルーティングにもし変更があれば、再度同じコマンドを実行すれば上書きされます。

また作成した.routeファイルは、チームで共有する必要はないと思いますので.gitignoreに追加すると良いです。

.gitignore
.route #追記



以上、小技ではありますが最後までお読みいただきありがとうございます〜!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】ルーティングの状態をファイルで見やすく確認する小技

環境

バージョン等
Laravel 6.20.19
MacOS 10.15.7 Catarina

①普通にルーティングを確認してみる

Laravelの通常のWebページとしてのルーティングはRoutes/web.phpにいろいろと記載していますが、
prefixとかgroupeなどを記述していると、実際にどうなっているか分からなくなったりしますよね。

これを確認する時に使うのが、

ターミナル
$ php artisan route:list

というコマンド。

なのですが、これがずら〜と表示されてちょっと分かりづらい。

Before

image.png

②見やすくルーティングを表示する

ということでこれをファイルとして保存して見やすく表示してみましょう。
route:listコマンドに> .routeを追加すればOKです。

ターミナル
$ php artisan route:list > .route

これでプロジェクトの直下に.routeというファイルが保存されました。

After

中身を表示するとこんな感じで見やすく表示されます。
image.png
作成してしまえばその後はコマンドを実行せずに確認できるのも嬉しいですね。
なおルーティングにもし変更があれば、再度同じコマンドを実行すれば上書きされます。

また作成した.routeファイルは、チームで共有する必要はないと思いますので.gitignoreに追加すると良いです。

.gitignore
.route #追記



最後までお読みいただきありがとうございます〜!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP if文とcount()の混合条件の作成

はじめに

今回は基本的な投稿システムが完了したので少しこだわりを入れて備忘録として投稿いたします。これを利用してlaravelに組み込みます。

今回の条件

  1. 1件以上のコメントがある場合は現在〇〇件コメント数という風に表示
  2. コメントが0件の場合は現在〇〇件コメント数を非表示にする

これらの条件を使用するにはif文とcount()の両方を使用することになります。以下にサンプルを掲載します。

if文の構文

if.php
$value1 = 200;
$value2 = 100;

if ($value1 > $value2){
  echo 'value1はvalue2より大きい';
  $value2 = $value1;
}

countの構文

count.php
$collection = collect(['鈴木', '佐藤', '田中']);
echo $collection->count();  // 3

上記の条件を纏めたコード

sample.php
@if ($post->comments->count() >= 1)
<p>
  <span class="badge badge-primary">
    現在のコメント数 {{ $post->comments->count() }}
  </span>
</p>
@endif

“スクリーンショット” 2021-03-29 17.29.14.jpg

解説

今回は各々が投稿した投稿内容の詳細にコメントを掲載するようになっています。
$postは投稿の変数になります。commentsはPost.modelのcommentsを表しています。
>= 1で1件以上のコメントがある場合は不等号を使用して表示させる様にしています。

Post.php
    public function comments()
    {
        // 投稿は複数のコメントを持つ
        return $this->hasMany('App\Models\Comment');
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

switch文撲滅委員会はこの度match式推奨委員会になりました

はじめに

昔こんな記事を書いたことがあります。
switch文撲滅委員会
switch文には罠が多く極力使わない方が良い、ということを言いたくて書いたものです。
この度PHP8からmatch式が使えるようになりました。
switch文を撲滅してmatch式を推奨していきたいのでこの記事を書きました。

match式とは

公式ドキュメント
switch文と比較すると以下の違い(利点)があります。

switch match
比較 曖昧 厳密
break 必要 不要
例外値 スルー 例外

サンプルコード

比較

前回の記事で出したサンプルコード

sample1_switch.php
<?php
$sample = 1;
switch($sample){
case true:
    echo 'これはtrueです'."\n";
    break;
case 1:
    echo 'これは数値の1です'."\n";
    break;
default:
    echo 'これは1ではありません'."\n";
    break;
}

// 結果
// これはtrueです

このコードでは曖昧比較であるためcase trueの部分に入ってしまうのが課題でした。
ではmatch式で書くとどうなるでしょうか?

sample1_match.php
<?php
$sample = 1;
$return_value = match ($sample) {
    true    => 'これはtrueです',
    1       => 'これは数値の1です',
    default => 'これは1ではありません',
};
var_dump($return_value);

// 結果
// string(25) "これは数値の1です"

自動で厳密比較をしてくれました。

フォールスルー

sample2_switch.php
<?php
$sample = 'a';
switch($sample){
case 'a':
    echo 'これはaです'."\n";
case 'b':
    echo 'これはbです'."\n";
case 'c':
    echo 'これはcです'."\n";
default:
    echo 'これはa,b,cではありません'."\n";
}

// 結果
// これはaです
// これはbです
// これはcです
// これはa,b,cではありません

このコードではbreakを書いていないため全ての分岐が評価されるのが課題でした。
ではmatch式で書くとどうなるでしょうか?

sample2_match.php
<?php
$sample = 'a';
$return_value = match ($sample) {
    'a'     => 'これはaです',
    'b'     => 'これはbです',
    'c'     => 'これはcです',
    default => 'これはa,b,cではありません',
};
var_dump($return_value);


// 結果
// string(16) "これはaです"

breakなしでもフォールスルーされませんでした。

例外値

sample3_switch.php
<?php
$sample = 'd';
switch($sample){
case 'a':
    echo 'これはaです'."\n";
case 'b':
    echo 'これはbです'."\n";
case 'c':
    echo 'これはcです'."\n";
}

// 結果なし

defaultが未定義でどのケースにも合致しない場合、switch文ではスルーされていました(何も起きない)
ではmatch式で書くとどうなるでしょうか?

sample3_match.php
<?php
$sample = 'd';
$return_value = match ($sample) {
    'a'     => 'これはaです',
    'b'     => 'これはbです',
    'c'     => 'これはcです',
};
var_dump($return_value);

// 結果
// PHP Fatal error:  Uncaught UnhandledMatchError: Unhandled match value of type string in sample3_match.php:3
// Stack trace:
// #0 {main}
//   thrown in sample3_match.php on line 3

どのケースにも合致しないとUnhandledMatchErrorの例外が投げられます。
これで意図しないケースにすぐに気付くことができ、より堅牢にコードを作成することが可能となりました。

まとめ

match式は今までのswitch文の罠だった部分をキレイに回避できるため、大変おすすめです。
PHP8を使っている方は是非match式で書いていきましょう!

付録

ローカルの環境をphp8にしたくなかったので挙動を試せるdocker環境を用意しました。
ご自由にお使いください。
https://github.com/yamamoto-hiroya/php8_practice

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP5上級試験/準上級試験の上級合格に挑戦(14) プログラミングPHP第3版 3章〜4章

はじめに

  • 3章関数に関しては、黒本に書いていないことはほぼないため省略し、4章文字列から
  • 4章文字列だと、しれっと黒本に書いてない関数が出てきたりする
  • 後半だと正規表現が出てくる。黒本だと18章。正規表現は結構使うので頑張って身につけていきたいところ。

4.1 文字列定数のクォート処理

バックスラッシュでエスケープされる対象が未知のものであった場合それはそのまま表示される

index.php
$str = "これは\c なんだ?";
echo $str;

出力
これは\c なんだ?

4.2.3 printf

  • 各書式に合わせて文字列を出力可能(小数第1位切り捨てとか、日付とか、0付き数値とか、16進数形式など)
  • 出力せず、変数に文字列として返したい場合はsprintf()を使う。
index.php
$date = new DateTIme('now');
$year = $date->format('Y');
$month = $date->format('m');
$day = $date->format('d');
$str = sprintf('%4d/%02d/%02d',$year,$month,$day);
echo '今日は' . $str . 'です';

出力
今日は2021/03/25です

※もっと効率的なやり方あると思うけど今回はこれで

4.2.4 print_r()とvar_dump()

  • GLOBALSのような再帰構造をもつものだと、print_fは無限ループになってしまうが、var_dump()は3回までやってそのあとは出力切り捨てになる

4.3 個別文字へのアクセス

strlen()を使い、Helloという文字を1文字ずつ出力させよ。

index.php
$string = 'Hello';
for ($i=0; $i < strlen($string); $i++){
  printf("%d番目の文字は %s です<br>",$i,$string{$i});
  printf("%d番目の文字は %s です<br>",$i,$string[$i]);
}

※これは私も驚いたが、変数{}や変数[]で、中にある数値(0から)でそれぞれの順番にある文字が抜き出せる仕様らしい

4.4.1 空白の除去

空白の除去といえばtrim系関数だが、これにはオプションがつけられる。
デフォルトだと前後の空白しか削除しないが、オプションを指定するといろいろなものが削除可能。

index.php
$record = "  Fred\tFlintstone\t35\tWilma\t   \n";
$record = trim($record, " \r\n\0\x0B");
echo $record;

出力
Fred Flintstone 35 Wilma
// Fred\tFlintstone\t35\tWilma にならない?? というより\tが除去されてる感じ
  なぜ??

4.6.2 類似性

2つの文字列が似ているかを調べる関数がphpには存在する
そこで問題

(1) ある単語が英語でどう発音されるかに基づいた値を出力する関数は?
(2) (1)のうち性能がよいのは?
(3) 2つの文字列に共通している文字の数を返す関数は?
(4) 2つの文字列を一致させるために何文字を修正すればいいかを計算することで類似性を出力する関数は?
 
 
 
 

解答

(1) soudex(),metaphone() サウンデックス・メタフォン
(2) metaphone()
(3) similar_text()
(4) levenshtein() リーベンシュタイン

以下、例
なお、soundexとmetaphoneだが、同時に使うことはできず、比較する時は必ずどちらかに統一しなければならない。

index.php
//(1)
$name1 = "Fred";
$name2 = "Phred";

if (soundex($name1) == soundex($name2)){
  print "soundex: {$name1}{$name2}は似てる!<br>";
} else {
  print "soundex: {$name1}{$name2}は似てないな<br>";
}
if (metaphone($name1) == metaphone($name2)){
  print "metaphone: {$name1}{$name2}は似てる!<br>";
} else {
  print "metaphone: {$name1}{$name2}は似てないな<br>";
}

index.php
//(2)
$name1 = "Rasmus Lerdorf";
$name2 = "Razmus Lehrdorf";
$common = similar_text($name1,$name2,$percent);
printf("2つの文字列は %d 文字(%.2f%%)一致",$common,$percent);
index.php
//(3)
echo $similarity = levenshtein('cat','cot');

4.7.3.3 sscanf

printfはフォーマットに沿って表示させるが、こっちは、フォーマットに沿って文字列を分解し、配列に入れるという動作を行う。
たとえば、次の例は日時を分解した例。

index.php
$x = '12/31 23:59';
$y = sscanf($x,'%d/%d %d:%d');
echo $y[0], '月';
echo $y[1], '日';
echo $y[2], '時';
echo $y[3], '分';

出力
12月31日23時59分

下はオプションの変数をつけた場合、返り値は要素(フィールド)数になる。

index.php
$x = '12/31 23:59';
$y = sscanf($x,'%d/%d %d:%d',$month,$day,$hour,$minute);
echo "{$y} 個のフィールドに分解しました<br>";
echo "{$month}{$day}{$hour}{$minute}分";

4.7.4.3 マスクを使う関数

黒本にも載ってる内容だが、strcspn()はNULバイトやタブ、キャリッジ・リターンが含まれてないかを調べるのに有効。

index.php
echo strcspn($str,"\n\t\0") != strlen($str);

というように使う。
NULバイトやタブ・キャリッジ・リターンが含まれていないかを調べることができる。

4.8 正規表現

正規表現=検索のパターンを表す文字列。このパターンを別の文字列と比較し、その文字列がそのパターンにマッチするか調べる関数がある

使用方法としては大まかに「マッチした情報のゲット」、「マッチした内容を別の文字列への置換」、「マッチした文字列をいくつかに分離する」という3つがある。

PHPで使用可能な正規表現

(1) PHPで使用可能な正規表現を3つ
(2) (1)はそれぞれどのような拡張モジュールを持っているか?
(3) (1)の各正規表現の構文モードは?
(4) 日本語を含むマッチングのために必要なものは?
 
 

解答
(1) mbstringの正規表現、Perl互換の正規表現,POSIXの正規表現
  ※なおPOSIXはPHP5.3.0で非推奨
(2) mbstring拡張モジュール、PCRE(ペクル)拡張モジュール、regex(リジェックス)拡張モジュール
  ※mb_で始まる関数、preg_で始まる関数、ereg_で始まる関数である。
  ※PCREはC言語のライブラリ。
(3) Ruby,Perl,regex 
  ※デフォルトだが、切り替えも可能
(4) mbstring以外だと、文字エンコーディングにUTF−8,オプション「U」を指定する

正規表現の基本

  • NULLバイトが文字列の終わりの記号と判断され、認識されない時がある
    →バイナリセーフな関数で対応可能
  • 文字クラス[]/選択肢|/繰り返し/アンカー/デリミタ/量指定子は基本。
    参考:サルでもわかる正規表現
    ありがとうございます!
  • 言明(assertion)とは、パターンの前後に対する条件付け。アンカー、言明サブパターン(4.8.15参照)がある。

(1) 量指定子で{n,m} {n} {n,} の意味は?
(2) アンカーで、[[:<:]] [[:>:]] \b \B \A \z \Z の意味は?
(3) 文字クラスで\s \S \w \W \d \D の意味は?
 

 
 
 
 
 
 
 
 
 
  
解答
(1) 最低n回、m回以下 最低n回、n回以上
(2) 単語先頭/単語末尾/ワードの境界/ワードの境界以外/文字列の先頭/文字列の末尾(改行の直前)/文字列の末尾 
(3) 空白文字/空白文字以外/ワード文字/ワード文字以外/数字/数字以外

文字クラス

(1) 照合順序とは?
(2) 等価クラスとは?

解答
(1) ロケール、つまり地域によっては特定の文字列を1つの文字として扱うことがあるが、これを照合順序という。

たとえば、スペイン語でchは1つの文字として扱われている。 a,b,ch,c、d・・というように。
とすると、文字クラスで[st[.ch.]]は、sかtかchのどれかにマッチするということになる。

(2) 等価クラスは、たとえば[a/a(ウムラウトつき)/a(アクセントつき)]の場合、並び順はaという区切りで行われる。

4.8.11 キャプチャしないグループ

(?:サブパターンさせる言葉)とする
下記はキャプチャさせるグループとキャプチャさせないグループの比較

index.php
echo preg_match("/(ello)(.*)/","jello biafra",$match1);
print "<br>";
echo $match1[1];
print "<br>";

echo preg_match("/(?:ello)(.*)/","jello biafra",$match2);
print "<br>";
echo $match2[1];

出力
1
ello
1
biafra

理由として、まず1つ目でマッチするのは
ello biafra,ello,biafraの3つ。
2つめは
ello biafra,biafraの2つだけ。
理由はおそらく、1つ目の場合、(ello)がキャプチャされ、2つ目で出力されたからだろう。
2つ目の場合はelloがキャプチャされなかったので出力されず、biafraだけ出力されたってことか。

4.8.12 後方参照

パターンの中で取得したテキストを同じパターンで後の方にないか探すことができる

index.php
echo preg_match("/([[:alpha:]]+)\s+\\1/","Paris in the the spring",$m);
print "<br>";
echo $m[1];

出力
the

まず最初と最後の/はデリミタ。
( )で囲まれたサブパターンは、アルファベットが1文字以上というのを表している。
そしてその後の\sはスペース。が+で一文字以上続くことを表す。
\1はバックスラッシュはエスケープで、後方参照、つまりさっきマッチした同じ文字が出てくることを表している。
これを全部確認すると、
たとえば、Paris Parisとか in in、the the とかspring springだったら、マッチするということ。
で、ここではthe theがマッチする。
ここで、echoは、$m[1]、つまりサブパターンなので、theが出力される。

ここは理解するのに悪戦苦闘した(こちら参照)
助けていただいた@blue32aさん、ありがとうございました。
要するにパターンがコピーされるというわけではないというのを勘違いしてたということ。
つまり、/([[:alpha:]]+)\s+\1/ は、/([[:alpha:]]+)\s+([[:alpha:]]+)/じゃないの? という。
そうではなくて、()内で取得できたワードがそのまま\1にくる。それがパターンになる。

4.8.13 後置オプション

閉じデリミタの後に置ける1文字のオプション。これによって挙動が変わる。

index.php
echo preg_match("/cat/i", "Stop,Catherine!"); 

出力
1
Catherineの「Cat」は大文字であるが、大文字小文字を区別しなくなったのでcatとマッチする。

以下はどんな挙動を起こすものか?

(1) i
(2) s
(3) x
(4) m
(5) e
(6) U
(7) u
(8) X
(9) A
(10) D
(11) S
 

 

 

 
解答
(1) 大文字小文字を区別しない
(2) ピリオドを改行を含めた任意の文字にマッチさせるようにする。  
  通常は改行は含めない。
(3) パターンから空白・コメントを除去する
(4) キャレットは各改行直後の文字、ドルは各改行直前の文字にマッチさせる。(いわゆる複数行モードのこと)
(5) 置き換え文字にPHPを指定すると、そのコードをevalした結果で文字列を置き換える。
(6) 貪欲さの指定を反転する。*や+の挙動を貪欲さを最低にする
(7) パターンをUTF-8として扱う
(8) バックスラッシュ直後に特殊文字でないものが続くとエラーを起こすようにする。
(9) パターンの最初の文字が^であるように文字列の先頭にアンカーを設定する
(10) $を行末のみマッチさせるようにする
(11) パターンを解析をじっくりして、次からの処理を早くする。

なお、オプションは複数設定できる。たとえば/imのように。

4.8.14 インラインオプション

上記以外にもパターンの一部にだけ適用するオプションを設定できる。
フォーマットは(?(フラグ名):(パターン))
使えるのはi,m,s,U,x,X

index.php
echo preg_match('/I like (?i:PHP)/','I like pHp');

もちろん1を返す。
逆に上記でオプションの前にハイフン(-)をつけることでその設定を無効にできる。

index.php
echo preg_match('/(?-i:I like) PHP/i','I like pHp');

もちろん1を返す。

index.php
echo preg_match('/I like (?i)PHP/',I like pHp');
echo preg_match('/I (like (?i)PHP)/',I like pHp a lot',$match);
echo $match[1]; 

※ただし、このインラインオプションが設定された部分(カッコ内)はキャプチャの対象にならず、キャプチャしたい場合は、別に()を用意する必要がある。

4.8.15 先読みと戻り読み

もし次(前)の文字が○○だったら、これをマッチさせるという機能。
文字列を分解するときに便利
これらはどういう意味か?

  • パターンX(?=パターンY)
  • パターンX(?!パターンY)
  • (?<=パターンY)パターンX
  • (?<!パターンY)パターンX

 

解答
+ Xの直後でYがマッチする
+ Xの直後でYがマッチしない
+ Xの直前でYがマッチする
+ Xの直前でYがマッチしない

下記は例

index.php
$message = preg_split('/(?=^From )/m' $mailbox);

メールボックスでメール内容を取得する方法。
Fromで始まるところが分離できる点なのでこれが効果的。

4.8.16 無視

繰り返しを含む式を更に繰り返さないようにする
FMTは(?>subpattern)。
つまりマッチした部分を更に繰り返さないようになる。
ただ、出力方法は以前と変わらず、時間が短くなるだけ。
下記は前者が無視を使わないパターンの例、後者が無視を使ったパターンの例
どちらも出力はN。
時間が短くなるかどうかはこのサンプルでは測れず。

sample.php
$p = '/(a+|b+)*\.+$/';
//$p = '/(?>a+|b+)*\.+$/';
$s = 'ababbbababababababababababababa..!'
if (preg_match($p,$s)){
  echo 'Y';
}
sample2.php
/*$p = '/(a+|b+)*\.+$/';*/
$p = '/(?>a+|b+)*\.+$/';
$s = 'ababbbababababababababababababa..!';
if (preg_match($p,$s)){
  echo 'Y';
} else {
  echo 'N';
}

 

4.8.17 条件式

正規表現で使用可能なif文のようなもの。
(?(条件)yespattern)
(?(条件)yespattern/Nopattern)
※なお、条件として使用可能なのは後方参照(1~99まで)・先読み・戻り読みのみ。

index.php
(?(?=[^a-z]*[a-z])
\d{2}-[a-z]{3}-\d{2}  |  \d{2}-\d{2}-\d{2} )

上記はYes/Noを使ったもの。
最初ので、数値が続いた後にそれがあってもなくてもよく、その後にアルファベットが続くものにマッチするか調べる。
つまり、数値のあとに少なくとも英字が1文字以上あるかを調べる。
この場合、-があるかどうかは条件式の時点では問題ではない、ということ。

マッチしたら、次にYesの場合はdd(数字)-aaa(アルファベット)-dd(数字)にマッチするか調べる
Noの場合はdd(数字)-dd(数字)-dd(数字)にマッチするか調べる。

この場合、dd(数字)-aaa(アルファベット)-dd(数字)か、dd(数字)-dd(数字)-dd(数字)に当てはまるものがあれば
マッチする。
たとえば、11-aaa-22とか、22-33-44とかにマッチするということ。

4.8.18 関数

ここでいう関数は正規表現に関連する関数ということ。

1) 正規表現に関する関数は5種類に分けられるか、どんなカテゴリか。
2) マッチングの関数は?
3) 置き換えの関数は?
4) 分割の関数は?
5) フィルタリングの関数は?
6) クォートの関数は?
7) Perl互換の正規表現とPerlの正規表現の違いは?
 
 
 
 
 
 
 
 
解答
1) マッチング・置き換え・分割・フィルタ・テキストのクォートの5つ。
2) preg_match() Perl形式のパターン。
  preg_match_all() 
3) preg_replace()
4) preg_split()
5) preg_grep()
6) preg_quote()
7) パターン文字列内でNull文字は扱えない
 Perlの\E,\G,\L,\i,\Q,\u,\Uはサポートしていない
(?(Perlのコード))はサポートしていない
 \D\G\U\u\A\x\
\v(垂直タブ)はサポートしている
 先読み・戻り読み言明は*・+・?で繰り返せない
 否定言明で使用した()付きのサブパターンにマッチした内容は記憶されない
 戻り読み言明で選択肢を使う場合は、異なる長さの文字列を選択肢として選べる。

4.8.18.1 マッチング

preg_matchについては結構使ってきているので割愛
FMTはこちら

index.php
preg_match_allpattern,target_string,matches[,order];

target_stringをpatternで検索し、マッチしたすべての文字列をorderで指定した順番にmatchesで指定した配列に入れるというのが具体的な処理内容。
orderは2つのオプションが有る

1) PREG_PATTERN_ORDER(デフォルト)

$matches[0] は 1 回目のマッチングでキャプチャした値の配列、 $matches[1] は 2 回目のマッチングでキャプチャした値の配列。

index.php
preg_match_all("|<[^>]+>(.*)</[^>]+>|U",
    "<b>example: </b><div align=left>this is a test</div>",
    $out, PREG_PATTERN_ORDER);
echo $out[0][0] . ", " . $out[0][1] . "<br>";
echo $out[1][0] . ", " . $out[1][1] . "<br>";

最初は「<」で、その次は「>」以外(※キャレットがあるから)のすべての文字が1文字以上で、次に「>」があり、次にどんな文字でも良いから1文字以上、次に</がきて、次はさっきのと同じで、>が来る。Uは貪欲さをなくすので、最小でマッチするものを探すということになる。
そんなわけで、\$match[0]は、<b>example: </b>と、<div align=left>this is a test</div>の2つ。
次にサブパターン、つまり(.*)の部分だから、example:と、this is a testの2つがくることになる。

これはHTMLで抜き出すときに便利だな

2) PREG_SET_ORDER

$matches[0] は 1 回目のマッチングでキャプチャした値の配列、 $matches[1] は 2 回目のマッチングでキャプチャした値の配列、 といった順序。

index.php
preg_match_all("|<[^>]+>(.*)</[^>]+>|U",
    "<b>example: </b><div align=left>this is a test</div>",
    $out, PREG_SET_ORDER);
echo $out[0][0] . ", " . $out[0][1] . "<br>";
echo $out[1][0] . ", " . $out[1][1] . "<br>";

説明は上のとおりだが、配列の順番が変わる。
そんなわけで、\$match[0]は、<b>example: </b>と、example:が入り、
\$match[1]は<div align=left>this is a test</div>と、this is a testがくる

つまり1)はタグのみの配列と、タグに囲まれた文字列のみの配列、
2)はマッチングしたタグとそのタグに囲まれた文字列を配列に入れている、ということになる。

4.8.18.2 置換

replaceはWordの検索・置き換えと同じで、文字列中からパターンにマッチする内容を別の内容に変更する。

index.php
preg_replace (pattern , replacement , target[, limit, count])

target に関して pattern を用いて検索を行い、 replacement に置換。
その回数はlimitになり、実際の置き換え回数がcountに入る。
limitのデフォルトは-1でこれはマッチしたものすべてに対して置き換えを行う。

index.php
$string = 'April 15, 2003';
$pattern = '/(\w+) (\d+), (\d+)/i';
$replacement = '${1}1,$3';
echo preg_replace($pattern, $replacement, $string);

数字リテラルがあとに続く場合、\1の代わりに{1}を使うと正常に対応可能。
ちなみに上のパターンにはスペースが各()と()の間にあるので注意。
よって、{1}(\1)はApril,\2は15,\3は2003をキャプチャしている。
そこからリプレイスを行うと出力は「April1,2003」となる

次はcount引数を使った場合

index.php
$count = 0;

echo preg_replace(array('/\d/', '/\s/'), '*', 'xp 4 to', -1 , $count);
echo $count; //3

出力は
xp***to
3

まず パターンは 数字とスペースをに置き換えるということ。
結果としてxp
**toになった。
最後は置き換えた回数が入るので、3になる。

4.8.18.3 分割

・pret_split() 取り出す各部分を区切る文字が予めわかっている時はこれを使う
上にある記号をマッチした箇所で区切る。
FMTはpreg_split(pattern,string [,limit [,flags]]);

index.php
$ops = preg_split('{[+*/-]}','3+5*9/2');
var_dump($ops);

この場合は+,*,/で区切るということなので、
array(4) { [0]=> string(1) "3" [1]=> string(1) "5" [2]=> string(1) "9" [3]=> string(1) "2" }
とオペランドが表示される。

flagsは3つある
+ PREG_SPLIT_NO_ENTRY・・・区切り文字を返さない
+ PREG_SPLIT_DELIM_EMPTY・・・分断された部分が空文字ならリターンしない
+ PREG_SPLIT_DELIM_CAPTURE・・・パターンにマッチした箇所を返す

index.php
$ops = preg_split('{([+*/-])}','3+5*9/2',-1,PREG_SPLIT_DELIM_CAPTURE);
var_dump($ops);

内容的にはかわらないが、パターンにマッチした箇所を返す、というので、出力は下記のようになっている。
array(7) { [0]=> string(1) "3" [1]=> string(1) "+" [2]=> string(1) "5" [3]=> string(1) "*" [4]=> string(1) "9" [5]=> string(1) "/" [6]=> string(1) "2" }

index.php
$array = preg_sprit('//',$string);

これだと、文字列のすべての文字の間にマッチし、1文字ずつ抜き出す形になる。

4.8.18.4 フィルタリング

指定された配列の要素のうちパターンにマッチするものだけを返す。
下の例だと、末尾が.txtになるファイルだけを取得する。

index.php
$matching = preg_grep(pattern,array);

//ex
$textfiles = preg_grep('/.txt$/',$filenames);

4.8.18.5 クォート

指定された文字列にのみマッチする正規表現を作成

index.php
$re = preg_quote(string [, delimiter]);

//ex
echo preg_quote('$5.00 (five bucks)');

こうすると、出力は
\$5\.00 \(five bucks\)になる
つまり特別な意味を持つ文字(*や$など)をstringで指定するとエスケープする。

index.php
$toFind = '/user/local/etc/rsync.conf/'
$re =preg_quote($toFind, '/');
if (preg_match("/{$re}/",$filename)){
 echo 'みつかりました!';
}

上のようにオプション2番めのところにクォートを行う文字を指定すると、エスケープする。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel6 チートシート

はじめに

Laravel触り始めたばかりの初心者です。
いちいち調べるのもめんどくさくなってきたので基本的な部分をまとめてみました。
自分用のチートシートにはなりますが、Laravel6で開発中の初心者の方も参考にしてください。
開発環境に関しては、MAMPで構築しています。

Laravelのインストールから初期設定

Laravelのインストール

Laravelのインストールはcomposerコマンドで行います。

ターミナル
composer create-project laravel/laravel (フォルダ名) --prefer-dist "6.0.*"

composer create-projectには2つのオプションがあります。
1. --prefer-dist
2. –prefer-source

--prefer-distは、zipファイルでダウンロードします、こっちのほうが高速なのでこちらを採用。
--prefer-sourceは、git cloneでソースを落とします。

Laravelの動作確認

Laravelのインストールが正常に行われたかを確認するために開発サーバーを起動します。

ターミナル
php artisan serve

初期設定

Laravelでやっておくべき初期設定は以下の7つです。

  1. タイムゾーン
  2. 言語設定
  3. DBの文字コード
  4. デバッグバー
  5. DB設定
  6. エラーメッセージの日本語訳
  7. HTMLにCSRFトークンの設置

タイムゾーン

タイムゾーンを日本時間に変更します。

config/app.php(70行目あたり)
'timezone' => 'Asia/Tokyo'

言語設定

言語設定を日本語に変更します。

config/app.php(83行目あたり)
'locale' => 'ja'

DBの文字コード

文字コードをUTF-8に変更します。

config/database.php(55行目~56行目あたり)
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci'

デバッグバー

デバッグ用にデバッグバーを表示させます。

ターミナル
composer require barryvdh/laravel-debugbar

現時点での設定では本番環境でもデバッグバーが表示されます。
DBなどの値がユーザーに見えてしまうため本番環境ではデバッグバーを非表示にする必要があります。

.env
APP_DEBUG=false

DB設定

.envファイルのDBに関する記載の部分を修正していきます。

.env
DB_CONNECTION=mysql //DB種類
DB_HOST=127.0.0.1 //ホスト
DB_PORT=3306 //ポート
DB_DATABASE=laravel //データベース名
DB_USERNAME=root //ユーザー名
DB_PASSWORD= //パスワード
接続確認
ターミナル
php artisan migrate
エラー発生

修正すべきポイントは3つです。

①.envのDB_HOSTを修正

.env
DB_HOST=localhost

②config/database.phpのunix_socketを修正

config/database.php(54行目あたり)
'unix_socket' =>  '/Applications/MAMP/tmp/mysql/mysql.sock'

③ターミナルでキャッシュクリア

ターミナル
php artisan cache:clear
php artisan config:cache

エラーメッセージの日本語訳

resources/lang/en内にエラーメッセージのファイルがあるがこれらすべて英語となっています。
そのため、日本語訳するために以下のサイトからダウンロードします。

ダウンロードしたファイルをresources/lang/ja内に格納します。

任意修正箇所

エラーメッセージが一部英語で表示されます。

スクリーンショット 2021-03-29 12.46.06.png

エラーメッセージの変更は以下のように行います。

resources/lang/ja/validation.php
"attributes" => [
  "password" => "パスワード"
]

スクリーンショット 2021-03-29 12.48.11.png

HTMLにCSRFトークンの設置

こちらを入れておかないとフォームが必ずエラーとなります。
HTMLのheadタグ内の共通レイアウトbladeファイルを作り、そこに記載します。

resources/views/layout.blade.php(レイアウトファイル)
<meta name="csrf-token" content="{{ csrf_token() }}">

フロントエンドでVue.jsを使うとき

Laravel6ではインストールが必要となりました。

ターミナル
// laravel/uiをインストール
composer require laravel/ui:^1.0 --dev

// どちらかを選択
// ①auth機能あり
php artisan ui vue --auth
// ②auth機能なし
php artisan ui vue

// コンパイルとインストール
npm install && npm run dev

npm run devでエラーが発生した場合

エラーメッセージは以下の通りです。

ERROR  Failed to compile with 2 errors                                                             

 error  in ./resources/sass/app.scss

【原因】

sass-loaderのバージョンの互換性がない。

【対応】

バージョンの変更を行う

ターミナル
// sass-loaderをアンインストール
npm uninstall --save-dev sass-loader

// sass-loaderをインストール
npm install --save-dev sass-loader@7.1.0

// 実行
npm run dev

MVCアーキテクチャ

大きく分けて2つの動きが考えられます。

①DBの情報を必要としないとき
クライアント → ルーティング → コントローラー → ビュー

②DBの情報を必要とするとき
クライアント → ルーティング → (コントローラー ⇄ モデル ⇄ DB) → ビュー

ルーティング


フォルダ
routes/web.php
apiの場合は、routes/api.php

routes/web.php
// ①直接Viewに移動
// Viewのwelcome
Route::get("/", function() {
  return view("welcome");
}

// ②Controllerに移動
// ControllerのUserControllerのindexメソッドに移動
Route::get("/user", "UserController@index");

コントローラー


フォルダ
app/Http/Controllers

コントローラーの作成方法

ターミナル
php artisan make:controller UserController

リソースコントローラー(RESTful)

1行のコードで典型的なCRUDルートをコントローラーで作成できます。

ターミナル
php artisan make:controller PhotoController --resource
動詞     URI アクション ルート名
GET /tests index Tests.index
GET /tests/create create Tests.create
POST /tests store Tests.store
GET /tests/{test} show Tests.show
GET /tests/{test}/edit edit Tests.edit
PUT/PATCH /tests/{test} update Tests.update
PUT/PATCH /tests/{test} destroy Tests.destroy
RestFulでよく使われる書き方

認証がされているときに表示する

routes/web.php
Route::group(["prefix" => "photos", "middleware" => "auth"], function() {
   Route::get("index", "PhotosController@index")->name(Photos.index);
   Route::get("create", "PhotosConroller@create")->name(Photos.create);
   Route::posts("store", "PhotosController@store")->name(Photos.store);
   Route::get("show/{id}", "PhotosController@show")->name(Photos.show);
   Route::get("edit/{id}", "PhotosController@edit")->name(Photos.edit);
   Route::post("update/{id}", "PhotosController@update")->name(Photos.update);
   Route::post("destroy/{id}", "PhotosController@destroy")->name(Photos.destroy);
})

idの値をどのようにして取ればいいのか?

resources/views/show.blade.php
<form action="{{ route('test.edit', [ "id" => $test->id ]) }}" method="GET">
</form>

値の取り出し方

値の取り出し方は2通りあります。
①Viewのフォーム経由から値を受け取る
②ModelでDBから値を受け取る

View経由
app/Http/Controllers/UserController
use App\Models\User;

public function store(Request $request) {
    // 値を取得する
    // $email = $request->input("email");
    // $password = $request->input("password");

    // 正しく取得できているか確認
    // dd($email);

    // Modelを呼び出すだけでOK
    $user = new User;

    // インスタンス化したものに保存
    $user->email = $request->input("email");
    $user->password = $request->input("password");

    // DBに保存
    $user->save();

    // リダイレクト
    return redirect("user/index");
}

POSTがうまくいかないとき
一旦キャッシュをすべてクリアしてみましょう。

ターミナル
php artisan optimize:clear
Model経由
app/Http/Controller/UserController.php
// ModelsのUserを読み込み
use App\Models\User

// クエリビルダーを使うためのおまじない
use Illuminate\Support\Facades\DB

// ①Eloquent
// all() = すべてのデータを取得する
$values = User::all();

// find() = 引数の値のidのデータを取得する
$value = User::find($id);

// where() = 条件指定(categoryカラムがPHPのデータを取得する)
$value = User::where("category", "PHP")->get();

// ②クエリビルダー
// get() = 条件にあったデータのすべてを取得する
$values = DB::table("users")->get();

// first() = 最初の1件を取得する
$value = DB::table("users")->first();

// SELECT = 取得するカラムを指定する
$values = DB::table("users")->select("name", "email")->get();

// user.indexのViewに移動し、$valuesの値を渡す
return view("user.index", compact("values"));

モデル


フォルダ
app/Models

モデルの作成方法

同時にマイグレーションファイルを作成すると便利

ターミナル
php artisan make:model Models/Post --migration
マイグレーションとは?

マイグレーションとは、DBテーブルの履歴を管理する仕組みです。


フォルダ
database/migrations

マイグレーションの頻出カラムタイプ
database/migrations/XXXX_XX_XXXXXX_create_users_table.php
public function up()
    {
        Schema::create('registers', function (Blueprint $table) {
            // 符号なしBIGINTを使用した自動増分ID
            $table->bigIncrements('id')->comment('id');
            // 20文字以内の文字列
            $table->string('name', 20)->comment('名前');
            // 255文字以内で独自の文字列
            $table->string('email', 255)->unique('email')->comment('メールアドレス');
            // 日付
            $table->date('birthday')->comment('生年月日');
            // 0~255までの整数値で符号なし
            $table->tinyInteger('gender')->unsigned()->comment('1:男性 2:女性');
            // 最大65535文字で未記入可
            $table->text('memo', 65535)->nullable()->comment('メッセージ');
            $table->timestamps();
        });
    }

テーブルにカラムを作成する

ターミナル
php artisan migrate
マイグレーションを修正する

①migrationファイルを新規に作成する
--tableオプションでテーブル名を指定します。
ファイル名は何をするかをわかりやすくするのがおすすめです。
下記の場合、「usersテーブルにemailを追加する」です。

ターミナル
php artisan make:migration add_email_users_table --table=users

②migrationファイルを修正

database/migrations/XXXX_XX_XXXXXX_add_email_users_table.php
public function up()
    {
        Schema::table('users', function (Blueprint $table) {
             $table->string('email');  //カラム追加
        });
    }

public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('email');  //カラムの削除
        });
    }

③migration

ターミナル
php artisan migrate

ビュー


フォルダ
resources/views

assetsヘルパー

Bladeファイル内にCSSやJSを記述すると可読性が著しく落ちるので別ファイルに保存します。

public 
  |__ css
  |__ js
  |__ img
resources/views/index.blade.php
<!-- css -->
<link rel="stylesheet" href="{{ asset('/css/style.css') }}">

<!-- js -->
<script src="{{ asset('/js/index.js') }}"></script>

<!-- img -->
<img alt="ロゴ" src="{{ asset('/img/logo.jpg') }}">

ダミーデータ

Factory & Faker


フォルダ
database/factories

ターミナル
php artisan make:factory PostFactory

ここで修正が必要です

database/factories/PostFactory.php
// これは誤り
// use App\Model

// こちらが正しい
use App\Models\Post

// ModelではなくPostのためこちらも誤り
/* $factory->difine(Model::class, function (Faker $faker) {
    return [

    ];
} */

//こちらが正しい
$factory->define(Post::class, function (Faker $faker) {
    return [
        "name" => $faker->name,
        "title" => $faker->realText(50),
        "email" => $faker->unique()->email,
        "tel" => $faker->phoneNumber,
        "password" => $faker->password,
        "url" => $faker->url,
        "gender" => $faker->randomElement(["0", "1"]),
        "age" => $faker->numberBetween($min = 1, $max = 6),
        "contact" => $faker->realText(200)
    ];
});

その他のFakerに関しては以下を確認してください。

作成手順

①config/app.phpの修正
config/app.php(109行目あたり)
"faker_locale" => "ja_JP"
②シーダの作成
ターミナル
php artisan make:seeder PostSeeder
PostSeeder.php
use App\Models\Post

public function run() {
    // 200個作成
    factory(Post::class, 200)->create();
}
③データベースシーダーに追記
database/seeds/DatabaseSeeder.php
public function run()
{
    $this->call([
        // ここに付け足していく
        UsersTableSeeder::class,
        PostsSeeder::class,
    ]);
}
④ターミナル
ターミナル
// Composerのオートローダーを再生成する
composer dump-autoload

// データベースに入っている値を初期化し入力する場合
php artisan migrate:fresh --seed

バリデーション


フォルダ
app/Http/Requests

ターミナル
php artisan make:request StoreBlogPost
app/Http/Requests/StoreBlogPost.php
// ①trueに変更(これをしないとうまく作用しない)
public function authorize() {
    return true;
}

public function rules() {
    return [
        // 【必須】最大20文字
        "name" => "required|string|max:20",
        // 【必須】最大255文字のメールアドレスであり被りがあってはならない
        "email" => "required|email|unique:users|max:255",
        // 【必須】8文字以上で確認用と同じパスワード
        "password" => "required|confirmed|min:8",
        // 【任意】
        "hobby" => "nullable",
    ]
}
app/Http/Controllers/StoreBlogPostController
// ファイルを読み込む
use App\Http\Requests\StoreBlogPost;

// これで自動的にバリデーションができるようになる
public function store(StoreBlogPost $request) {
}
resources/views/blogpost.blade.php
// ①それぞれ書く
@error("email")
    <span>
        <strong>{{ $message }}</strong>
    </span>
@enderror

// ②まとめて書く
@if ($errors->any())
    <div>
        <ul>
            @foreach($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP fopenでファイルを開いて使用する

fopenの例

読み込み・追加の処理

txt.txt

abcdefg
one,two,three
12345
アイウエオ

fopen.php

<?php 
//$変数名(ファイルポインタ) = fopen (“開きたいファイル名”, “オープンモード”);

// 読み込みモードでファイルを開く
$fp = fopen("txt.txt", "r");

// ファイルを1行ずつ取得する
while ($line = fgets($fp)) {
  echo "$line";
}

// ファイルを閉じる
fclose($fp);

//結果
/*
abcdefg
one,two,three
12345
アイウエオ
*/

//追加の記述(末尾に追加される)
$fa = fopen('txt.txt','a');
fwrite($fa,"\n".'5行目に追加');
fclose($fa);

$fr = fopen('txt.txt','r');
while($line = fgets($fr)){
    echo $line;
}
/*
//結果
/*
abcdefg
one,two,three
12345
アイウエオ
5行目に追加
*/

上書きの処理

さらにこの記述の後に以下のコードを実行すると上書きされる。

$fw = fopen('txt.txt','w');
fwrite($fw,'上書きして1行になります');
fclose($fw);

$fr = fopen('txt.txt','r');
while($line = fgets($fr)){
    echo $line;
}
fclose($fr);
//結果
//上書きして1行になります

fopen()の第二引数について

以下の用途で指定する
参考:phpドキュメント
スクリーンショット 2021-03-29 15.33.16.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP 関数は同一ファイル内であれば宣言より前に記述しても呼び出し可能

関数の呼び出しについてのメモ

例)

<?php 
echo "上です\n";
hoge();

function hoge(){
    echo "hogeですファイル内から呼べます\n";
    echo "関数は同一ファイルであれば宣言より前に記述しても呼べます\n";
}

echo "下です\n";
hoge();
//結果
/*
上です
hogeですファイル内から呼べます
関数は同一ファイルであれば宣言より前に記述しても呼べます
下です
hogeですファイル内から呼べます
関数は同一ファイルであれば宣言より前に記述しても呼べます
*/

宣言の前後で関数が呼び出せていることを確認できる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP 関数は同一ファイル内であれば宣言より前に記述しても呼び出し可能。呼び出しは大文字と小文字を区別しない。

関数の呼び出しについてのメモ

例)

<?php 
<?php 
echo "上です\n";
hoge();

function hoge(){
    echo "hogeですファイル内から呼べます\n";
    echo "関数は同一ファイルであれば宣言より前に記述しても呼べます\n";
}

echo "下です\n";
hoge();

echo "関数は大文字と小文字を区別しない\n";
HOGE();

echo "小文字と大文字を混ぜても平気\n";
hoGe();

/*
上です
hogeですファイル内から呼べます
関数は同一ファイルであれば宣言より前に記述しても呼べます
下です
hogeですファイル内から呼べます
関数は同一ファイルであれば宣言より前に記述しても呼べます
関数は大文字と小文字を区別しない
hogeですファイル内から呼べます
関数は同一ファイルであれば宣言より前に記述しても呼べます
小文字と大文字を混ぜても平気
hogeですファイル内から呼べます
関数は同一ファイルであれば宣言より前に記述しても呼べます
*/

宣言の前後で関数が呼び出せていることを確認できる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP unset(), implode(), explode(), in_arrayの使い方

各関数の意味

unset

変数の割り当てを解除する

implode

配列を引数に指定した文字で区切り文字列として結合する

explode

文字列を指定した値を区切りにして配列に分割する

in_array

配列の中から指定した値があるか確認する

例)

<?php 


//unset
$foo = 'test';
echo $foo."\n";
unset($foo);// 変数を削除する
echo 'hoge'."\n";
echo $foo."unset\n";
echo 'hoge2'."\n";

/*
test
hoge
unset //unset()によってtestunsetとならず「unset」のみがechoされている
hoge2
*/


//implode
$arr = ["one","two","three"];
echo implode($arr)."\n";// onetwothre ←第一引数に分割する値を入れないと配列を単純に結合する。
echo implode(",", $arr)."\n";// one,two,three ←引数を指定した事で第一引数の「,」を配列のインデックスごとに挿入して結合している


//explode
$str = "one,two,three"; // one,two,three
var_dump(explode(",", $str));
/*
array(3) {
  [0]=>
  string(3) "one"
  [1]=>
  string(3) "two"
  [2]=>
  string(5) 
  */
//↑第一引数で指定した「,」で文字列を分割し配列を作っている


$search_arr = ['remon','stroberry','apple'];

if(in_array('apple',$search_arr)){
    echo 'リンゴがあります';
}else{
    echo 'リンゴはありません';
}
//リンゴがあります
//第一引数で指定した値が配列内にあればtrueを返す(なければfalseを返す)。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ボクとPHP・Laravel・Dockerとの約200時間

始まりは突然に…

あれは、ある企業に面接したときの事でした。それまで、私はRubyでの開発を行っており、ポートフォリオもRailsで作成していました。その面接で、ポートフォリオの説明を行い面接終了時に次のようなお言葉を頂いた時から始まりました。
あなたに課題を出します。それを期限を決めませんが行ってください。とのことでした。課題とは以下の通りです。
Image from Gyazo

:fire:それを成し遂げた理由:fire:

それは、至って単純な話です。
やってみたい・挑戦してみたいと思ったかです!
やりたいやってみせるという気持ちで望みました!

で、一体何から着手したのか

プロセスは以下の通りです。
①プロゲートの初級(PHP基礎学習)

②ドットインストールPHPに関するもの全て(PHP基礎学習)

③ドットインストールDockerに関するもの全て(Docker基礎学習)

④ドットインストールLaravel基礎の部分のみ(Laravel基礎学習)

⑤Docker環境下でLaravel立ち上げYouTube等の動画を元に実践(Laravel実践学習)

⑥ちょっとした掲示板を作成(Laravel実践学習)

⑦ポートフォリオ作成(ErrorStocker作成)
この様な流れで実施
以下が着手時間です。学習管理に関しては、Studyplusを利用しました。
・PHP基礎学習に費やした時間:31時間11分(2月6日〜2月11日)
・Docker基礎学習に費やした時間:8時間10分(2月7・11・12日)
・Laravel基礎学習に費やした時間:31時間24分(2月11〜18日)
・ErrorStocker作成に費やした時間:130時間4分(2月18日〜3月18日)
合計時間:約200時間

?作成したポートフォリオ

題名:ErrorStocker

URL:http://error-st.com
GitHub:https://github.com/miyaseinto/ErrorStokcer

何を目的に作成したのか??の前に軸を話させてください!

少し私の話になりますが少々お付き合いください!(どうでもいいんだよテメェの話なんかと思った人は飛ばしてください笑)
これは、私がエンジニアを目指した軸にあります。
【不便に感じたものをデジタル化でより楽ができるように便利に変えたい】これが私がエンジニアを目指した軸です。
なぜその様な軸ができたかというと、前職での公務員経験からこの様な考えが生じたからです。
前職の公務員は超田舎で勤務していました。そのため、デジタル化が都会より著しく劣っており何をするにもアナログで不効率でした。何より住民の方が大変な思いをすることが多々ありました。回覧物だったり、各種手続きだったり、仕方がないことだと考えていました。ですが、エンジニアという職業がそれらを解決に導く職業であるのでは無いかと調べるうちに、それを担ってやりたいという思いが日に日に増していきました。
で、それを勉強したいと思い勉強していく中で自分が書いたコードがブラウザ画面で変化していくことにとてもやりがいを感じ、これを仕事にしたいと考え公務員を退職しエンジニアを目指そうと思い勉強に励みました。
退職した職場には申し訳なかったのですが、自分の腹の中を上司の方々に話すとそれならば挑戦してみろと背中を強く押してくれました。ですので、その思いを全部背負っとるじゃいと思いながらエンジニアになりたいと考えてます。これがエンジニアを目指した軸のお話です。(長々と申し訳ないです笑)

作成背景&目的

上記の軸を基に、プログラミングの勉強をしている時に不便に感じたことを便利に変えるために作成しました。
私が、エラーと遭遇した時にリファレンスサイトやQiitaの内容からエラー解決に導こうとします。そこで、いつもは一度エラーした内容をGoogleのブックマークの中にファイルごとに保存して、もう一度確認をしたい時に見直しをしておりました。しかし、情けない話そのファイルをどこに保存したのかがわからなくなり、探すことに時間を掛けることがありました。そこで、検索をかけてその内容を短時間で探せることはできないかと考え作成に至りました。

工夫した・苦労したPOINT

・投稿内容をマークダウンで投稿できるようにしたこと(リンクを文字列内に入れたいと考えたため)
・上記と同じだが、コメント機能にもマークダウンを使用したこと(上記と同じ)
・投稿した時間を表記させたこと
・コメントした時間を表記させたこと
・Bootstrapを使用したが、Bootstrap感を排除したこと
・写真の圧縮を行ったこと
・タグを一覧でも表示させたこと
・閲覧用としてログイン簡略化させたこと(通常のログインと表示は異なる)
・ページネーションを導入したこと
・キーワード検索をタイトルの内容と本文の内容で検索できるようにしたこと
・キーワード検索で検索件数を表示させたこと

?機能一覧

ユーザー機能
・ユーザー登録(投稿用ログイン)
・ゲストログイン(閲覧用ログイン)
・マイページにて以下の投稿の一覧表示
・自分の投稿内容
投稿機能
・エラーのストックをログインアカウントが投稿・編集・削除
・一覧表示、詳細表示
・投稿一覧表示で10個の投稿数をページネーションを実施
・写真投稿及び圧縮(intervention/image)
・タグ付け(タグ検索)
・キーワード検索(タイトル・内容)
・投稿内容にマークダウンを採用(cebe/markdown)
コメント機能
・投稿にコメントを投稿・編集・削除
・投稿詳細ページにコメント一覧表示
・コメント内容にマークダウンを採用(cebe/markdown)

?使用技術

フロントエンド
・HTML / CSS / Bootstrap
バックエンド
・PHP 8.0.2
・Laravel 8.28.1
データベース
・Mysql 8.0
開発環境
・Docker 20.10.2
・docker-compose 1.27.4
本番環境
・AWS(VPC、EC2、S3、Route53)
・Nginx

:computer:各種解説内容投稿

Dockerの内容解説
マークダウン導入内容解説
投稿内容の解説

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ポートフォリオの解説投稿(自分なりのアウトプットです!!)

自分が書いたコードを説明できないのは恥だよ!!

これは、あるYouTuberの言葉です。
確かにそうですよね…
はい、やります。やらせてください!
解説やらせてください!
てな感じで始めます笑
と、その前にこちらがポートフォリオの内容です。

三部構成でいきます!

Controller・Model・Viewの三部構成で解説していきます。
主にTweetの内容からです。

まずControllerから行きます!

app/Http/Controllers/TweetController.php
namespace App\Http\Controllers;
#namespaceとは、名前空間といい、こちらを使用してクラス被りをしないようにする。というもの。

use App\Models\Tweet;
use App\Models\Tag;
use App\Models\Comment;
#各種のModelを読み込む際の記述
use Storage;
use Illuminate\Support\Str;
#ストレージファイルの読み込む際の記述
use InterventionImage;
use Image;
#写真の編集ライブラリ(Intervention Image)を使用したため、その読み込みの記述
use Illuminate\Http\Request;
use App\Http\Requests\TweetRequest;
#Requestファイル(バリデーションのルールが書いてあるファイル)を読み込む際の記述

class TweetController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {

        $q = \Request::query();
        #$qにリクエストのクエリストリングを入れる。

        if(isset($q['tag_name'])){#もし、$qの中のtag_nameに値があればtrueを実行する
            $tweets = Tweet::with(['user', 'tags'])->latest()->where('tag_box', 'like', "%{$q['tag_name']}%")->paginate(10);
            #$tweetsにN+1問題を解決するためにwithを使用し[userとtags]のModel情報を取得し、最新の情報を取得し、カラム名tag_boxを文字列検索で$q['tag_name']を取得し、ページネーションで10ページを表示させる。
            #withとはリレーションを解決したい時に使用する。
            #whereの引数について、第1引数はカラム名です。第2引数はデータベースがサポートしているオペレーターです。第3引数はカラムに対して比較する値である。
            $tags = \DB::table('tags')->get();
       #$tagsにtagsテーブルの情報を取得する。

            return view('tweets.index', [
                'tweets' => $tweets,
                'tags' => $tags,
                'tag_name' => $q['tag_name']
            ]);
       #返り値でViewに値を渡す。
        }else {

            $tweets = Tweet::with(['user', 'tags'])->latest()->paginate(10);
            $tags = \DB::table('tags')->get();

            $tags_name = [];
            foreach ($tags as $tag) {
                array_push($tags_name, $tag->tag_name);
            }
            #tags_nameに配列として値を渡す。foreachで値を回し、タグテーブルの情報からtag_nameを配列に追加していく。

            return view('tweets.index', [
                'tweets' => $tweets,
                'tags' => $tags
            ]);


        }
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('tweets.create'
        );
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(TweetRequest $request)
    {
        $tweet = new Tweet;
        $tweet->user_id = $request->user_id;
        $tweet->content = $request->content;
        $tweet->tag_box = $request->tag_box;
        $tweet->title = $request->title;
        #$tweetにパラメーターで取得した情報をそれぞれ値を入れていく。

        if($request->hasFile('image')){#パラメーターの中にimageが入っていたらtrueで値を返す。
            $filename = $request->file('image');#リクエストでimageファイルとして$filenameに入れる。
            $name = $filename->getClientOriginalName(); #$filenameの画像の名前を取得する
            $ext = strtolower(substr($filename->getClientOriginalName(), strrpos($filename->getClientOriginalName(), '.')+1));#strtolowerで大文字を小文字に戻し、substrで文字を切り出す。これで、写真の拡張子を取得する。
            if(!in_array($ext, ['png', 'jpg', 'gif', 'jpeg'], true)) {#['png', 'jpg', 'gif', 'jpeg']の配列に$extの値があるかチェックする。
                $tag_view = '画像以外のファイルが指定されています。画像ファイル(png/jpg/jpeg/gif)を指定して下さい';
                return view('tweets.tag', compact('tag_view'));
                #tweets.tagのViewにcompactでtag_viewの情報を渡して表示させる。
            }

            $imageFile = time(). '_' . $name;
            $imagePath = storage_path('app/public/') . $imageFile;
            $image = Image::make($filename)#imageを作成する
                ->resize(1000, null, function ($constraint) {
                    $constraint->aspectRatio();#横幅を1000にして縦横比を保持したまま変更を行う。
                    $constraint->upsize();#小さい写真を無理やり1000にすることをせずにそのままのサイズを維持する。
                })
                ->orientate()#画像の向きを自動的に調整する。
                ->save($imagePath);#その情報を一旦ローカルに保存する。

            $path = Storage::disk('s3')->putFile('myprefix',$imagePath, 'public');#config/filesystems.phpの中に設定した情報からawsのs3にファイル名は自動で生成し保存する。
            $tweet->image = Storage::disk('s3')->url($path);

            Storage::disk('local')->delete('app/public/' . $imageFile);#ローカルの情報を削除する。
        } 

        preg_match_all('/#([a-zA-Z0-90-9ぁ-んァ-ヶー一-龠]+)/u', $request->tag_box, $match);#ハッシュタグで始まる単語を取得。結果は、$matchに多次元配列で代入される。

     # $match[0]に#(ハッシュタグ)あり、$match[1]に#(ハッシュタグ)なしの結果が入ってくるので、$match[1]で#(ハッシュタグ)なしの結果のみを使う。
        $tags = [];
        foreach ($match[1] as $tag) {
            $found = Tag::firstOrCreate(['tag_name' => $tag]);#firstOrCreateメソッドで、tags_tableのnameカラムに該当のない$tagは新規登録される。
            array_push($tags, $found);#$foundを配列に追加します(=$tags)
        }

        #投稿に紐付けされるタグのidを配列化
        $tag_ids = [];
        foreach ($tags as $tag) {
            array_push($tag_ids, $tag['id']);
        }

        $tag_count = count($tag_ids);#tagの数を取得する。
        if ($tag_count <= 5){#もしtagの数が5以下ならtrueを実施
            $tweet->save();
            $tweet->tags()->attach($tag_ids);#投稿ににタグ付するために、attachメソッドをつかい、モデルを結びつけている中間テーブルにレコードを挿入

            return redirect('/top');
        } else{
            $tag_view = 'タグ数が5つ以上ですので変更してください。';
            $tweet_id = $id;
            return view('tweets.tag-edit', compact('tag_view','tweet_id'));
        }
    }

    /**
     * Display the specified resource.
     *
     * @param  \App\Models\Tweet  $tweet
     * @return \Illuminate\Http\Response
     */
    public function show(Tweet $tweet)
    {

        $tweetid = $tweet->id;
        $comments = Comment::where('tweet_id', '=', $tweetid)->get();#commentの情報をtweet_idと$tweetidの情報が同じならその情報を取得する。

        return view('tweets.show',[
            'tweet' => $tweet,
            'comments' => $comments,
        ]);

    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  \App\Models\Tweet  $tweet
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        $tweet = Tweet::with(['user','comments'])->findOrFail($id);#[userとcomments]のModel情報を取得し、$id(パラメーター)の情報から取得する。
        return view('tweets.edit',[
            'tweet' => $tweet,
        ]);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \App\Models\Tweet  $tweet
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request)
    {
        #ここはstoreクラスとほとんど同じ
        $id = $request->tweet_id;
        $tweet = Tweet::findOrFail($id);
        $tweet->content = $request->content;
        $tweet->title = $request->title;
        $tweet->user_id = $request->user_id;
        $tweet->tag_box = $request->tag_box;

        if($request->hasFile('image')){
            $filename = $request->file('image');
            $name = $filename->getClientOriginalName(); 
            $ext = strtolower(substr($filename->getClientOriginalName(), strrpos($filename->getClientOriginalName(), '.')+1));
            if(!in_array($ext, ['png', 'jpg', 'gif', 'jpeg'], true)) {
                $tag_view = '画像以外のファイルが指定されています。画像ファイル(png/jpg/jpeg/gif)を指定して下さい';
                return view('tweets.tag', compact('tag_view'));
            }

            $imageFile = time(). '_' . $name;
            $imagePath = storage_path('app/public/') . $imageFile;
            $image = Image::make($filename)
                ->resize(1000, null, function ($constraint) {
                    $constraint->aspectRatio();
                    $constraint->upsize();
                })
                ->orientate()
                ->save($imagePath);

            $path = Storage::disk('s3')->putFile('myprefix',$imagePath, 'public');
            $tweet->image = Storage::disk('s3')->url($path);

            Storage::disk('local')->delete('app/public/' . $imageFile);
        }

        preg_match_all('/#([a-zA-Z0-90-9ぁ-んァ-ヶー一-龠]+)/u', $request->tag_box, $match);

        $tags = [];
        foreach ($match[1] as $tag) {
            $found = Tag::firstOrCreate(['tag_name' => $tag]);
            array_push($tags, $found);
        }

        $tag_ids = [];
        foreach ($tags as $tag) {
            array_push($tag_ids, $tag['id']);
        }


        $tag_count = count($tag_ids);
        if ($tag_count <= 5){
            $tweet->save();
            $tweet->tags()->sync($tag_ids);

            return redirect('/top');
        } else{
            $tag_view = 'タグ数が5つ以上ですので変更してください。';
            $tweet_id = $id;
            return view('tweets.tag-edit', compact('tag_view','tweet_id'));
        }

    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  \App\Models\Tweet  $tweet
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        #Tweetモデルから$idを見つけ出す。
        $tweet = TWeet::find($id);


        preg_match_all('/#([a-zA-Z0-90-9ぁ-んァ-ヶー一-龠]+)/u', $tweet->tag_box, $match);

        $tags = [];
        foreach ($match[1] as $tag) {
            $found = Tag::firstOrCreate(['tag_name' => $tag]);
            array_push($tags, $found);
        }

        $tag_ids = [];
        foreach ($tags as $tag) {
            array_push($tag_ids, $tag['id']);
        }


        $tweet->tags()->delete($tag_ids);#deleteで削除する。
        $tweet->delete();#上記と同じ
        return redirect('/top');

    }

    public function search(Request $request)
    {

        $tweets = Tweet::where('title' ,'like', "%{$request->search}%")
        ->orwhere('content' ,'like', "%{$request->search}%")
        ->paginate(10);#Tweetモデルから情報を取得し、where句を使用してパラメータで取得した値をtitleカラムとcontentカラムの中に同じ値があるかを取得する。でそのページネーションを10で表示させる。ものを$tweetsに入れる。

        $search_result = '【'. $request->search. '】の検索結果は'.$tweets->total().'件';#検索情報と検索にヒットした数を$search_resultに入れる。

        $tags = \DB::table('tags')->get();
        return view('tweets.index',[
            'tweets' => $tweets,
            'search_result' => $search_result,
            'search_query'  => $request->search,
            'tags' => $tags,
        ]);
    }
}

次はModelの解説していきます!

app/Models/Tweet.php
namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;


class Tweet extends Model
{
    protected $table = 'tweets';

    protected $fillable = [
        'title', 'user_id','tag_box','content', 'image',
    ];

    public function user(){
        return $this->belongsTo(\App\Models\User::class,'user_id');
    }#tweetとuserとの1対多の関係

    public function tags(){
        return $this->belongsToMany('App\Models\Tag');
    }#tweetとtagとの多対多の関係

    public function comments(){
        return $this->hasMany(\App\Models\Comment::class,'tweet_id', 'id');
    }#tweetとcommentとの1対多の関係
}

最後にViewの解説をします!

resources/views/tweets/index.blade.php
@extends('layouts.app')#部分テンプレート

@section('content')

<h2 class="card-header" style="text-align: center;">
  投稿一覧
</h2>


    @isset($search_result)#$search_resultの情報が入っていればtrueで実施
    <h5 class="card-title" style="text-align: center; padding-top: 30px; font-size: 20px; color: #55c500">{{ $search_result }}</h5>
    @endisset

<div class="row m-3">
        <div class="col-sm-3">
        <h5 class="card-title"><i class="fas fa-tags"></i>タグ一覧</h5>
            @foreach($tags as $tag)#Controllerから$tagsの情報を取得して$tagに入れて回す。
                <a href="{{ route('tweets.index', ['tag_name' => $tag->tag_name]) }}" class="btn btn-outline-success m-1">
                    {{ $tag->tag_name }}#tag_nameを表示させる
                </a>
            @endforeach
        </div>
        <div class="col-sm-9">
            @if (session('status'))
                <div class="alert alert-success" role="alert">
                    {{ session('status') }}
                </div>
            @endif
            @foreach ($tweets as $tweet)
            <div class="toast fade show" role="alert" aria-live="assertive" aria-atomic="true">
                <div class="toast-header">
                    <strong class="mr-auto">
                        <a href="{{ route('users.show', $tweet->user_id) }}" class="btn btn-outline-primary btn-sm">{{ "@".$tweet->user->name }}</a>
                        
                        <span class="text-muted" style="font-size:15px;">{{ $tweet->created_at->format('Y年m月d日')  }}にストック</span>
                    </strong>
                </div>
                <div class="toast-body">
                    <h5 class="card-title">
                        <i class="fas fa-tags"></i>
                        @foreach($tweet->tags as $tag)
                            <a href="{{ route('tweets.index', ['tag_name' => $tag->tag_name]) }}" class="badge badge-success">
                                #{{ $tag->tag_name }}
                            </a>
                        @endforeach
                    </h5>
                    <a href="{{ route('tweets.show', $tweet->id) }}" class="text-dark">
                        <h2 class="card-title">{{ $tweet->title }}</h2>
                    </a>
                </div>
            </div>
            @endforeach


            @if(isset($tag_name))#もし$tag_nameに情報があればtrueで実施
                {{ $tweets->appends(['tag_name' => $tag_name])->links() }}#tag_nameを基にペジネーションリンクにクエリ文字列を付け加えたいときは、appendsメソッドを使用する。linksメソッドは結果の残りのページヘのリンクをレンダーする。
            @elseif(isset($search_query))#もし$search_queryの値が入っていればtrueを実施
                {{ $tweets->appends(['search' => $search_query])->links() }}
            @else
                {{ $tweets->links() }}
            @endif

        </div>
</div>
@endsection

とりあえず、これでアウトプットとします。
自分なりのアウトプットですので間違い等はあるかもしれませんが、自分のための投稿しています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

パスからidを取得したい

背景

dreddでapiテストを作成している。
テストデータを作成するときに使用するidを取得したい。
そのためにパスからidを取得する。

環境

PHP 7.3.22

末尾のidを取得する

$path = '/users/30';
$id = intval(basename($path));

var_dump($id);

結果

int(30)

やっていること
basenaemでパスの末尾の文字列を取得する
intvalで整数型に変換する

idがパスの途中にある場合

idが末尾になるようにパスから末尾の文字列をトリムすればいい

$path = '/users/30/admin';
$id = intval(basename(rtrim($path, '/admin')));

var_dump($id);

結果

int(30)

ちょっと沼ったのでメモ

basename()の第2引数にsuffixを指定できる。
これを指定することで、指定した文字列を除いて末尾の文字列を取得できる。
参考
https://www.php.net/manual/ja/function.basename.php

そこで最初は以下のようにしていた

$path = '/users/30/admin';
$id = intval(basename($path, '/admin'));

var_dump($id);

その結果は以下のようになった。

int(0)

問題点

  • basenameのsuffixは'/'で区切った最末尾の文字列('admin')から'/admin'がその末尾にあれば、除くことになるので、この場合'admin'がbasenameした結果になる
  • intvalは失敗するとint(0)を返す

その結果、idが0のテストデータを作成していたため、テスト結果がエラーも出さず望んだ結果にならずに沼ってしまった。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHP】GET・POST サーバー 作成

今回はphpのget・postサーバーの作成について書きます。
正直、今回の記事は自身のメモとしての意味合いが大きいです。
似たようなことが書かれた記事は多いですし、コードの内容も多くないため簡潔に書いていこうと思います。

実装環境

今回はそんなに手間をかけるつもりはないため、xamppを使用します。
VSCodeを使用してコードを書きましたが、コマンドでビルドが必要だとかいうこともないので、テキストエディターは何でも良いです。

GET・POST サーバー

index.php
<?php
if (isset($_POST['post_sample'])) {
    echo $_POST['post_sample'];
}

if (isset($_GET['get_sample'])) {
    echo $_GET['get_sample'];
}

PHP側のコードはこれだけです。
$_POST['~']でPOSTで送られてきた値を取得し、$_GET['~']でGETで送られてきたデータを取得します。

クライアント側(html)

動作確認のために次はクライアント側のhtmlのコードを作成します。

index.html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UFT-8">
    <title>get_post_sample</title>
</head>

<body>
    <form action="index.php" method="GET">
        <input type="text" name="get_sample" value="get_sample_text">
        <input type="submit" value="get">
    </form>
    <form action="index.php" method="POST">
        <input type="text" name="post_sample" value="post_sample_text">
        <input type="submit" value="post">
    </form>
</body>

</html>

細かい説明は省きますが、formのinputを使用することでGET・POSTのいずれも送信することができます。(見てわかるかもしれませんが、methodの部分でGETとPOSTを定義してます)

動作確認

xamppを起動してページを呼び出せば下記の様なページになります。
image.png
ボタンのgetを押せばGETで送信され、ボタンのpostを押せばPOSTで送信されます。

GET送信時

image.png
php側の処理はechoしているだけなので、画面は送信したget_sample_textだけが表示されます。URLを確認してもらえばわかるかともいますが、GETの挙動としてパラメータがURLに表示されています。

POST送信時

image.png
php側で設定してる内容はGETとほぼ同じなので、挙動に大きな差は見られません。
POST時の挙動として、GETと違いパラメータがURLには表示されません。

クライアント側(JavaScript)

今回は、JavaScriptでもクライアント側を作成してみます。

script.html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UFT-8">
    <title>get_post_sample</title>
</head>

<body>
    <script>
        function do_post() {
            let xhr = new XMLHttpRequest();
            xhr.open('POST', 'index.php', true);
            xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
            let request = "post_sample=post_text_by_js";
            xhr.send(request);
            //post送信後のレスポンス
            xhr.onreadystatechange = function () {
                alert(xhr.responseText);
            }
        }
    </script>
    <input type="button" value="post" onclick="do_post()">
</body>

</html>

処理としてはXMLHttpRequestオブジェクトを生成してPOSTを送信(.send())します。
そのあと、onreadystatechangeに設定された内容の通りに、PHP側から送られてきたPOSTのレスポンスを処理します。

動作確認

image.png
作成したページを開き、postボタンを押下するとonreadystatechangeで処理を設定した通り、アラートが表示されます。
今回の場合は、アラートで表示される内容はphpのページで表示される内容と同じなので、postで送信した内容がそのまま表示されることになります。(php側のソースで、POSTで送られてきた内容をそのままechoしているだけになっているため)
htmlで作成した場合と違い、画面遷移することはありません。

終わりに

今回はGET・POSTのサーバー側と、クライアント側を二種類書きました。
簡潔に書くと言った割に長いような…
PHPはあまり触ってないので、個人的にはもう少し腕を磨きたいと思っています。
あと、GET・POSTというとJavaのサーブレットを仕事でやったことがありましたが、結構前のことで記憶が怪しいので、時間があればまとめようかと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む