20201108のPHPに関する記事は20件です。

配列とオブジェクトの違いについて

結論

配列オブジェクトでは、簡単に言うと書き方呼び出し方が違います。

配列 ( 連想配列 )

配列 = keyとvalueがセット
[ ]配列を宣言し、keyvalue=>(ダブルアロー演算子)で結ぶ。
呼び出しは、$array['key'];echoで表示させる。

$array = [ //配列$arrayを宣言
    "name" => "ワタナベ", //name(key)にワタナベ(value)を代入
    "number" => 1,
];

↓ 呼び出し

echo $array['name']; //ワタナベが表示される。

オブジェクト

オブジェクト = クラスの中にプロパティを宣言
{}クラスの宣言をし、new演算子インスタンス化をして値を持たせる。
new演算子 = インスタンスを作成する演算子
インスタンス化 = オブジェクトを作ること
呼び出しは、->(シングルアロー演算子)プロパティを指定

class Kazoku { //クラス名:1文字目は大文字
  public $name; //プロパティ:クラス内で使える変数を定義
  public $age;

  function __construct($name, $age){ //コンストラクター:プロパティに引数を代入
    $this -> name = $name; // nameプロパティ($this->name)に、渡ってきた引数($name)を代入している
    $this -> age = $age;
  }
}

$kazoku1 = new Kazoku("佐藤", "20"); //new演算子でインスタンス化(オブジェクト$kazoku1を作成)

↓ 呼び出し

echo $kazoku1 -> name; // $kazoku1の中にあるname(佐藤)が表示される。 

メリット

配列
データの追加や削除が容易に行える。
オブジェクト
プロパティと共にメソッドを設定できる。
数百の関数の中から目的の関数を探すのは大変です。
しかし、オブジェクトであれば関数はクラスに紐づいているため、対象のクラスから容易にアクセスや修正が行えます。

参考元はこちら
(https://coinbaby8.com/different-between-object-and-array.html)
(https://webukatu.com/wordpress/blog/4839/#i-2)
(https://qiita.com/riotam/items/cb5406daa9a1c6a694da)

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

【Lumen】.envをconfig()で読み込む

はじめに

Laravelでは、.envから環境変数を読み込む際、env()が使われます。

ただし、このenv()で直接.envにアクセスするのは好ましくありません。

理由としては、Laravelは設定ファイルのキャッシュが存在する場合、
.envを読み込まずにキャッシュを参照する仕様になっているためです。

ではどうすればいいのかというと、タイトルにある通り、config()を使用します。

本記事では、このconfig()での.env読み込みについて、
Lumenで実装する方法についてまとめます。

※Laravelでの具体的な実装例やenv()を使ってはいけない理由などをブログに詳しくまとめられている方がいらっしゃいます。
本記事では詳細には触れないので、気になる方はこちらをご覧ください。
Laravel の .env の値は config() 経由で使う

Lumenでconfig()を使う

  • configディレクトリ配下に任意の名前のファイルを作成します。
config/hoge.php
<?php

return [
    'fuga' => env('FUGA')
];

ここでは、.envにあるFUGAという環境変数を登録することにします。

  • bootstrap/app.phpに以下のコードを追記
bootstrap/app.php
// 追記
$app->configure('hoge');

ここがLaravelとは違ってLumen独自の設定となります。
configure()の引数には、上で作成したファイル名を入れます。(今回は'hoge')

設定は以上です。
あとは環境変数を呼び出したいところで、以下のように呼び出せます。

config('hoge.fuga');

おわりに

Lumenを触ることになって間もないですが、やはりLaravelとは違う部分が所々に見られて、
戸惑うことが多いですね...

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

Warning: Invalid argument supplied for foreach()の対処法

エラー内容

Invalid argument supplied for foreach() = PHPのforeach文配列データを取り出そうとした場合に、配列のデータが入っていない時に出るエラー。

foreach文配列の要素がある分だけ繰り返し処理をするループ文。

解決方法

まずは、配列データが入っているかを判定する必要があります。
foreach文で使えるのは配列オブジェクトのみなので、(arry)を使用して変数を強制的に配列にしてあげることで対処できます。

配列とオブジェクトについては別の記事に投稿します。

foreach($stmt as $row){
  //処理内容
}

↓

foreach((array)$stmt as $row){
  //処理内容
}

参考元はこちら
(https://memocarilog.info/wordpress/5132)
(https://qiita.com/takuma-jpn/items/678876ad12b9ae9998ac)

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

DataTables+生PHP+MySQL のサイト構築例

本社オンプレ経由で情報を支社からも参照できる簡易ポータルを、生のPHPで書いてみた。
<table>タグでリスト表示しているページがあるなら、DataTablesにちょっと書き替えるだけで、それっぽいWebアプリケーションにリニューアルできるという一例。

環境

種類 内容
オペレーティングシステム Ubuntu 18.04
Webサーバ Apache 2.4
リレーショナルデータベース MySQL 5.7
プログラミング言語 PHP 7.2
フロントエンドフレームワーク DataTables, Bootstrap4, jQuery, FontAwesome
Web Appsフレームワーク なし

システム構成図

image.png

アプリケーション画面

Bootstrap対応したDataTablesを使用。
インクリメンタルサーチ、ページネーション、ソート機能が、面倒な作り込み無しに実装できている。
image.png

ソースコードもシェアするので参考になれば!
データベース接続パラメータ、SQL文、Font Awesome のKitコードを埋めればそのまま動くはず。

Code
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta name="author" content="MindWood">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <title>取引先検索</title>
  <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.21/css/dataTables.bootstrap4.min.css" />
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
  <style>
    main {
      padding: 80px 10px 40px;
    }
    .fixed-bottom {
      color: #eee;
      background-color: #163;
    }
  </style>
  <script src="https://kit.fontawesome.com/<your kit code>.js" crossorigin="anonymous"></script>
</head>
<body>
  <nav class="navbar fixed-top navbar-expand-sm navbar-dark bg-primary">
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarToggler1" aria-controls="navbarToggler1" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarToggler1">
      <a class="navbar-brand" href="#">XXXXXX情報ポータル</a>
      <ul class="navbar-nav mr-auto mt-2 mt-lg-0">
        <li class="nav-item active">
          <a class="nav-link" href="#"><i class="fas fa-search fa-fw"></i>取引先検索</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="sns.php"><i class="fab fa-twitter fa-fw"></i>SNS連携</a>
        </li>
      </ul>
      <a href="logout.php" class="btn btn-light my-2 my-sm-0"><i class="fas fa-sign-out-alt"></i> ログアウト</a>
    </div>
  </nav>
  <?php
  define('DB_NAME', '<your database name>');
  define('DB_USER', '<your database user>');
  define('DB_PASS', '<your database password>');
  $link = mysqli_connect('localhost', DB_USER, DB_PASS) or die('DBMS接続エラー');
  mysqli_select_db($link, DB_NAME) or die('DB選択エラー');
  $result = mysqli_query($link, "
SELECT xxxxx as contract_id, xxxxx as contract_date, ...
FROM xxxxx a
LEFT JOIN xxxxx b ON a.xxxxx = b.xxxxx
ORDER BY a.xxxxx;
") or die('クエリ実行エラー');
  ?>
  <main role="main">
    <table class="table table-striped" id="customer_list">
      <thead>
        <tr>
          <th>受託番号</td>
          <th>受託日</td>
          <th>企業名</td>
          <th>担当者名</td>
          <th>電話番号</td>
          <th>メールアドレス</td>
          <th data-toggle="tooltip" title="XXXXシステムの照会区分コードに準じる">照会区分</td>
          <th>照会内容</td>
          <th data-toggle="tooltip" title="当社担当エリア">担当支社</td>
        </tr>
      </thead>
      <?php
      while ($row = mysqli_fetch_array($result)) {
      ?>
        <tr>
          <td><?= $row['contract_id'] ?></td>
          <td><?= $row['contract_date'] ?></td>
          <td><?= $row['company_name'] ?></td>
          <td><?= $row['staff_name'] ?></td>
          <td><?= $row['phone_number'] ?></td>
          <td><?= $row['email'] ?></td>
          <td><?= $row['inquiry_code'] ?></td>
          <td><?= $row['inquiry_body'] ?></td>
          <td><?= $row['branch_office'] ?></td>
        </tr>
      <?php
      }
      ?>
    </table>
  </main>
  <footer class="fixed-bottom text-center">
    <small>Copyright &copy; by MindWood</small>
  </footer>
  <script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
  <script type="text/javascript" src="https://cdn.datatables.net/1.10.21/js/jquery.dataTables.min.js"></script>
  <script type="text/javascript" src="https://cdn.datatables.net/1.10.21/js/dataTables.bootstrap4.min.js"></script>
  <script type="text/javascript">
    // DataTablesの日本語化
    $("#customer_list").DataTable({
      destroy: true,
      language: {
        url: "https://cdn.datatables.net/plug-ins/3cfcc339e89/i18n/Japanese.json"
      }
    });
    // Bootstrapツールチップの初期化
    $('[data-toggle="tooltip"]').tooltip();
  </script>
</body>
</html>
<?php
mysqli_close($link);
?>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

composer のバージョンをアップデートする

アップデートコマンド

最新バージョンにアップデート (今だとバージョン 2.x へ)

composer self-update

バージョンを指定してアップデート (1.10.17 を指定する場合)

composer self-update 1.10.17

Command-line interface / Commands - Composer

エラーが発生する場合

下記のようなエラーが発生する場合

SHA384 is not supported by your openssl extension, could not verify the phar file integrity 

composer のバージョンが古すぎるため、コマンドで更新できません。再インストールが必要です。

公式ドキュメント通りにインストールしていれば、同じように。

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php -r "if (hash_file('sha384', 'composer-setup.php') === 'c31c1e292ad7be5f49291169c0ac8f683499edddcfd4e42232982d0fd193004208a58ff6f353fde0012d35fdd72bc394') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
php composer-setup.php
php -r "unlink('composer-setup.php');"

Mac/Linux であれば下記のように composer.phar をグローバルで使えるように。

mv composer.phar /usr/local/bin/composer

インストールできたか確認する。

$ composer -v
   ______
  / ____/___  ____ ___  ____  ____  ________  _____
 / /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/
Composer version 2.x.x
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

kubernetes上にLaravel環境を構築した際のメモ

前回記事の続きとなる。

kubernetes上に作成したLNMP環境に対してLaravelプロジェクトをデプロイした際のメモ。

Laravelプロジェクトの構築

今回、構築作業にヒューマンオペレーションが入り込むのを極力避けることを目的とし、Laravelプロジェクト作成をコンテナログインからのコマンド実行以外で実施することとした。

Laravelプロジェクトの初期化

Pod作成時にcommandから実施してしまうと、Pod起動の度にプロジェクトの作成がされてしまう。

ということで、Laravelプロジェクトの初期化限定で、Jobオブジェクトを利用することにした。

Jobの作成

Jobは、コンテナを利用し処理を実行するリソースである。

Jobにて作成されたコンテナは、マニフェストファイルに記載されているタスク完了後、削除される。

今回は1回限りのタスクを実行するので、completions(成功回数):1/parallelism(並列度):1/backoffLimit(失敗許容回数):0とする。

また、今回、spec.ttlSecondsAfterFinished: 30とし、Laravelプロジェクト作成後、30sでPodを削除するようにする。

これを利用し、phpポッドの利用するPVに対してLaravelプロジェクトのデプロイのみ行う。

apiVersion: batch/v1
kind: Job
metadata:
  name: laravel-init-job
  labels:
    app: lnmp
spec:
  # One Shot Task
  completions: 1
  parallelism: 1
  backoffLimit: 0
  # 実行後30sでJob実行Podを削除
  ttlSecondsAfterFinished: 30
  template:
    metadata:
      labels:
        app: lnmp
    spec:
      containers:
      - name: laravel-init
        image: laravel
        imagePullPolicy: IfNotPresent
        # laravelプロジェクトの初期構築
        command:
          - sh
          - -c
        args:
          - "composer create-project --prefer-dist laravel/laravel '' \
            && chmod 777 -R ./storage/ \
            && chmod 777 -R ./bootstrap/"
        volumeMounts:
          - name: php-persistent-storage
            mountPath: /var/www
      restartPolicy: Never
      volumes:
      - name: php-persistent-storage
        persistentVolumeClaim:
          claimName: php-server-pv-claim

上記Jobマニフェストファイルlaravel-init-job.yamlをデプロイ。

$ kubectl apply -f laravel-init-job.yaml

Job実行時の不具合

Job実行時に以下のエラーとなった。

Creating a "laravel/laravel" project at "./"
Installing laravel/laravel (v8.1.0)
As there is no 'unzip' command installed zip files are being unpacked using the PHP zip extension.
This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost.
Installing 'unzip' may remediate them.
  - Installing laravel/laravel (v8.1.0): Downloading (100%)
Created project in /var/www/public
> @php -r "file_exists('.env') || copy('.env.example', '.env');"
Loading composer repositories with package information
Updating dependencies (including require-dev)
Killed

なんかkilledとなり、インストールに失敗している。当然Laravelプロジェクトは作成されていない。

こちらの記事([PHP] composer install が killed で失敗するときの原因と対処)にドンピシャで答えが載っていた。

どうやらメモリが足らないのが原因だったらしい。今回、phpのデプロイメントにはメモリ制限は設けていないので、ホストのメモリがそもそも足りていないと思われる。

ということでminikubeサーバーのメモリを2.5Gから4.0Gへ増やし、マウントしていたボリュームphp-persistent-storage内のファイルを削除。

再びJobを実行することでLaravelの作成に成功した。

Jobを実行したPodが削除されない

spec.ttlSecondsAfterFinished: 30としたにも関わらず、Jobを実行したPodがstatus: terminatingのまま残り続けた。

調べてみると、こちらの記事(Kubernetes で Complete/Failed となった Job を自動削除する)にて説明がされていた。

どうやらTTLAfterFinishedは、フィーチャーゲートなる実験的機能であるらしい。

ということで、以下コマンドからminikubeを起動し、機能を有効化する。(kubernetes > v1.12以上である必要がある。)

$ minikube start --feature-gates=TTLAfterFinished=true

この状態でJobを実行することで、Podがコマンド終了後に削除されるようになった。

Laravel環境変数ファイル(.env)をConfigMap化

mysql-phpポッド間の通信をサービス経由で実行するにあたり、Laravelの.envファイルにサービス名を指定する必要がある。

そこで、.envConfigMapとし、コンテナ外部から設定できるようにする。

.envの修正

こちらの記事(docker-composeでLaravelの開発環境を整える方法とその解説)を参考にし、.envの以下箇所を修正

DB_CONNECTION=mysql
# mysql-php間の通信で利用するService名
DB_HOST=lnmp-mysql-service
DB_PORT=3306
DB_DATABASE=test_db
DB_USERNAME=user
DB_PASSWORD=password

ConfigMapの作成

以下コマンドを実行し、ConfigMap laravel-envを作成する。

$ kubectl create configmap laravel-env --from-file={ホスト上の.envの配置されているパス}

ConfigMapをマウント

phpのDeploymentに作成したConfigMapをマウントし、再度読み込む。

この際、以前作成したボリュームと、ConfigMapのマウントパスが被ってしまいエラーとなった。

そこで、こちらの記事(KubernetesのConfigMapを使ってWordpressのphp.iniを書き換える)を参照し、マウント先をsubPathを使用してファイル自体へと変更。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: lnmp-php-deployment
  labels:
    app: lnmp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: lnmp
  template:
    metadata:
      labels:
        app: lnmp
    spec:
      containers:
      - name: lnmp-php-deployment
        image: laravel
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 9000
        volumeMounts:
        - name: php-persistent-storage
          mountPath: /var/www
        - name: laravel-env
          mountPath: /var/www/.env
          # マウント先をファイル自体へと変更する。
          subPath: .env
      volumes:
      # サーバーログ出力先(nginxと共有)
      - name: php-persistent-storage
        persistentVolumeClaim:
          claimName: php-server-pv-claim
      # laravel .envをinject
      - name: laravel-env
        configMap:
          name: laravel-env
          items:
          - key: .env
            path: .env
            mode: 0644

その後、Deploymentの作成を実施した。

mysqlの初期化

dbの初期化を実施。これはphpポッド内でコマンドを実行して行った。(極力手作業を避けたいとはなんだったのか……?)

$ php artisan migrate

Migration table created successfully.というメッセージが表示され、php-mysqlポッド間の通信設定が正常に設定できていることが確認できた。

Laravelプロジェクトへのアクセス

これでLaravel環境に必要な物が一通り揃ったはずなので、アクセスしてみる。

以下コマンドを実行し、nginxのホスト上のポートを確認しアクセス。

[root@kube ~]# kubectl describe service/lnmp-nginx-service | grep NodePort
Type:                     NodePort
NodePort:                 <unset>  32626/TCP

Laravel初期画面が出た!!

(地味にLaravelがブラウザのダークテーマに対応しているのに驚いた。)

まとめ

Jobを利用し、Laravelプロジェクトの初期化を実施することができた。

今回はJobの実行タスクをプロジェクトの初期化のみとしたが、これをGithubから取得するタスクに変更したりすることで、実装環境の均等化を図ったりできるんだろか?

でもそうなれば、Jobの管理が大変になるような気もする。どうやっているんだろう?

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

LaravelとjQueryで非同期処理で実装するいいね機能を世界一わかりやすく解説してみたい

やりたいこと

ジャズライブの口コミを投稿できるサイトをチームで制作しています。
今回は、ユーザーの投稿にいいねをできる機能を実装したいです。結構苦労したので、初心者だけど、いや初心者だからこそ日本一わかりやすくメモを残しておきたいと思います。ご指摘などありましたら、ぜひ容赦無くよろしくお願いします。

Laravel 7です。

仕様

  1. ハートボタンとカウンターで構成する
  2. 各投稿が合計いくつのいいねを集めているかが表示される
  3. 下記行動ができるのはログインしているユーザーのみ
  4. いいねボタンが押されたらスタイルをピンクにし、いいねカウントの数字を1増やす
  5. もう一度押されたらスタイルと数字を元に戻す
  6. あるユーザーが1つの投稿に対していいねが押せるのは1回のみ
  7. 自分がいいねした投稿は常にピンクになっている

実装前のプログラムの挙動に関する疑問...

  • どの時点でサーバー側にリクエストを出せばいい?(非同期処理だとしたら、イベントが発生した直後?)
  • 非同期処理だとしたら、何回も押されたらその都度リクエストを出してサーバーと通信するのか??
  • 誰がリクエストを出す?(JavaScriptのEventListener?)
  • どんなリクエストを出せばいい?(review_id, いいね押されたよっていう何らかのサイン...でもどんな??)
  • 投稿が削除されたら、それに付されたいいねもDBから全て削除する(リレーションで設定可能)

こんなこともわからない状態からでもなんとか実装に至りました!

材料

僕はお恥ずかしながら、まずどんな材料が必要なのか?そこから想像するのが難しかったです。
で、結局はこんなのが必要でした↓

材料
View これは誰でもわかりますよね。
CSS いいねされたハートマークは色を変えたいところです。
Frontのスクリプト この辺は僕でも、サイトを動的にするには必要だってわかりました。非同期処理はajaxでjQueryがそれできると知っていたので、jQueryを採用。
DB いいね専用のテーブルを作ります。ある投稿がいくついいねを集めているかカウントするために必要です。今回はEloquentでModelを作成しusersとreviewsと1対多でリレーションする。
migration DBバージョン管理ファイルで、各開発環境でテーブルの自動生成をしてくれます。
Model 今回はEloquentを利用します。ModelはDBテーブルの内容を定義したり操作するクラスです。
Route ルートも要るんですね。ページが遷移しない機能は初めてで、ページが遷移しなくてもルートって要るんだ!と知りました...。
Controller コントローラも要るんですね。でも確かにコントローラなきゃ誰がDBと通信するの?て話ですよね。

では、実際にどう料理するか見てきましょう!

実装!

DB データベース設計

テーブル構造(必要なフィールドのみ抜粋)

users
    id
    created_at, updated_at
reviews
    id
    created_at, updated_at
    user_id
likes
    id
    created_at, updated_at
    user_id
    review_id

usersとreviewsが既に作成済という前提で、likesテーブルとリレーションを作っていきましょう。
ターミナルで下記コマンドで、モデルを生成します。-mもしくは--migrationオプションでマイグレーションファイルも同時に作成できます。

$php artisan make:model Like -m

Migration マイグレーション

DBのバージョン管理機能、マイグレーションファイルで、先のテーブル構造を作っていきます。

database/migrations/yyyy_mm_dd_xxxxxx_create_likes_table.php
class CreateLikesTable extends Migration
{
    public function up()
    {
        Schema::create('likes', function (Blueprint $table) {
            $table->id(); //bigIncrements('id')と同じ
            $table->timestamps(); //s複数形でcreated_atとupdated_atを生成

            $table->foreignId('user_id') //usersテーブルの外部キー設定
                ->constrained() //userテーブルのidカラムを参照するconstrainedメソッド
                ->onDelete('cascade'); //削除時のオプション

            $table->foreignId('review_id') //同じことをreviewsテーブルとも
                ->constrained()
                ->onDelete('cascade');
        });
    }

マイグレーションファイルが準備できたら、マイグレートを実行します↓

terminal
$php artisan migrate

Model モデル

app/Like.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Like extends Model
{
    public function user()
    {   //usersテーブルとのリレーションを定義するuserメソッド
        return $this->belongsTo(User::class);
    }

    public function review()
    {   //reviewsテーブルとのリレーションを定義するreviewメソッド
        return $this->belongsTo(Review::class);
    }
}

  • belongsToの中身User::class。::スコープ定義演算子。static、定数、オーバーライドされたクラスのプロパティやメソッドにアクセスできる。::classはclassキーワードと呼ばれ、クラスの完全修飾名を文字列で取得できる。つまり'App\Model名'が返ってくる。つまり'App\Model名'をパラメータにするのと同じことです。(むしろそれが今は標準?)

view ビュー

ビューを作る前に仕様の再確認です。
いいねをできるのはログインユーザーのみ。なので、①ログインユーザーと②ゲストでの条件分岐が必要。
さらに自分が一度いいねした投稿は、いつ来てもピンクなままでないとわからなくなってしまうので、①の中でもいいね済の投稿とそうでない投稿を条件分岐しないとなりません。
まとめると、3つの条件分岐が必要になります→(①-1 ログインユーザーでいいねの押されていない投稿、①-2 ログインユーザーでいいねの既に押された投稿、② ゲストユーザー)

<!-- head内 -->
<meta name="csrf-token" content="{{ csrf_token() }}">

<!-- body内 -->
<!-- 参考:$itemにはReviewControllerから渡した投稿のレコード$itemsをforeachで展開してます -->
@auth
  @if (!App\Like::where('user_id', Auth::user()->id)->where('review_id', $item->id)->first())
    <span class="likes">
        <i class="fas fa-music like-toggle" data-review-id="{{ $item->id }}"></i>
      <span class="like-counter">{{$item->likes_count}}</span>
    </span><!-- /.likes -->
  @else
    <span class="likes">
        <i class="fas fa-music heart like-toggle liked" data-review-id="{{ $item->id }}"></i>
      <span class="like-counter">{{$item->likes_count}}</span>
    </span><!-- /.likes -->
  @endif
@endauth
@guest
  <span class="likes">
      <i class="fas fa-music heart"></i>
    <span class="like-counter">{{$item->likes_count}}</span>
  </span><!-- /.likes -->
@endguest
  • auth, endauth, guest, endguestは認証ディレクティブと呼ばれるbladeの機能で、アクセスしているユーザがログインしているのかゲストかを判別できます。
  • iタグはfont-awesomeです。(音楽サイトなので、ハートではなく♫を使ってます)
  • 巷にはなぜかaタグを使用するコーディング例が多いですが不要です。aタグを入れると余計なページ遷移が起きるので、逆にうざかったのですが、aタグを使うメリットをぜひ教えてください。
  • iタグのdata-の属性はカスタムデータ属性と呼ばれるグローバル属性(HTMLのどのタグにも設定可能な属性)の一種。これを介して、HTMLとJavaScript、jQuery間などでデータをやりとりできます。ここではいいねをする対象の投稿のid(review_id)をjQueryに伝える役割を担います。

CSS

いいねマークにlikedクラスが付いた時のスタイルを適宜設定します。

styles.css
.liked {
  color: pink;
}

jQuery

like.js
$(function () {
  let like = $('.like-toggle'); //like-toggleのついたiタグを取得し代入。
  let likeReviewId; //変数を宣言(なんでここで?)
  like.on('click', function () { //onはイベントハンドラー
    let $this = $(this); //this=イベントの発火した要素=iタグを代入
    likeReviewId = $this.data('review-id'); //iタグに仕込んだdata-review-idの値を取得
    //ajax処理スタート
    $.ajax({
      headers: { //HTTPヘッダ情報をヘッダ名と値のマップで記述
        'X-CSRF-TOKEN' : $('meta[name="csrf-token"]').attr('content')
      },  //↑name属性がcsrf-tokenのmetaタグのcontent属性の値を取得
      url: '/like', //通信先アドレスで、このURLをあとでルートで設定します
      method: 'POST', //HTTPメソッドの種別を指定します。1.9.0以前の場合はtype:を使用。
      data: { //サーバーに送信するデータ
        'review_id': likeReviewId //いいねされた投稿のidを送る
      },
    })
    //通信成功した時の処理
    .done(function (data) {
      $this.toggleClass('liked'); //likedクラスのON/OFF切り替え。
      $this.next('.like-counter').html(data.review_likes_count);
    })
    //通信失敗した時の処理
    .fail(function () {
      console.log('fail'); 
    });
  });
  });
  • $()はjQueryのセレクターの書き方。$はjQueryの略です。
  • thisは変数の一種で、特殊な変数。プログラムが自動的に値を代入するもの。今回はイベントが発火した変数like=iタグが代入されています。
  • なぜ$this$が付くのか?jQueryにおいて、変数を宣言する際、$はjQueryオブジェクト(=$()でセレクトした要素)を入れるための変数宣言で、 $は必須ではないが、通常の変数と区別するのに通常使います。
  • .onはイベントハンドラー。第一パラメータにイベントの種類、第二パラメータにハンドラとして無名関数を取っています。
  • .dataはjQueryのメソッド。HTML内に仕込んだカスタムdata属性の値を取得することができます。ここでは投稿のid(review_id)を受け取ります。取得したい要素.data('カスタムdata属性の名前')という文法になります。
  • $.ajax()は、非同期処理を行うイベントハンドラ。$.はこれまたjQueryのこと。中のパラメータはコード内の説明ご参照。ここの大きな構文は、$.ajax().done().fail()です。通信に成功したら、doneメソッドを、失敗したらfailメソッドを実行します。
  • attr()は、attributeの略で日本語訳すると「属性」です。つまり、なんてことはない、指定された属性の値を取ってくるメソッドです。(日本人プログラマの不利なところは一回英単語の意味を知らないといけないとこだなと最近思ってます)
  • .next()は、セレクター。Sibling(兄弟)の後ろの要素を全て、つまり全ての弟を返します。その中から特定の要素を指定する場合は、パラメータで指定します。
  • obj.html(htmlString)は、obj(=英語でいうところの目的語)にhtmlStringをセットします。.done()のパラメータdataにはコントローラからreview_likes_countという名前の「新規いいね後の総いいね数」が(←あとでコントローラで見ていきましょう)、{review_likes_count : 1}というJSONの形で渡ってきます。それをdata.review_likes_countとい文法でプロパティにアクセスします。JavaScriptの記法では「変数名.プロパティ名(=key)」でそのオブジェクトの対応する値が取得できます。この.をドット演算子と呼びます。このサイトがわかりやすいです。
  • .fail()には、処理が失敗したときの指示をコールバック関数で書いておきます。コールバック関数とは、他の関数(ここではfail)に引数として渡される関数です。ここでは、単純にconsoleに'fail'という文字列をログを出力するというだけの実装にしています。

Route ルート

routes/web.php
Route::post('/like', 'ReviewController@like')->name('reviews.like');

Ajaxで指定したurlと整合性を取ります。

Controller コントローラ(いいねをする/撤回する)

LikeControllerを作った方が良かったのかもしれませんが、私は投稿を制御するReviewController.phpに作りました。

app/Http/ReviewContoller
public function like(Request $request)
{
    $user_id = Auth::user()->id; //1.ログインユーザーのid取得
    $review_id = $request->review_id; //2.投稿idの取得
    $already_liked = Like::where('user_id', $user_id)->where('review_id', $review_id)->first(); //3.

    if (!$already_liked) { //もしこのユーザーがこの投稿にまだいいねしてなかったら
        $like = new Like; //4.Likeクラスのインスタンスを作成
        $like->review_id = $review_id; //Likeインスタンスにreview_id,user_idをセット
        $like->user_id = $user_id;
        $like->save();
    } else { //もしこのユーザーがこの投稿に既にいいねしてたらdelete
        Like::where('review_id', $review_id)->where('user_id', $user_id)->delete();
    }
    //5.この投稿の最新の総いいね数を取得
    $review_likes_count = Review::withCount('likes')->findOrFail($review_id)->likes_count;
    $param = [
        'review_likes_count' => $review_likes_count,
    ];
    return response()->json($param); //6.JSONデータをjQueryに返す
}
  1. Authクラスに対してuserメソッドでログインしているユーザーのモデルインスタンスを返す。
  2. 投稿のidが、ビューのカスタムdata属性→jQuery Ajax経由で$requestで渡ってきているので、それをキャッチします。
  3. このユーザーがこの投稿に既にいいねをしていれば、そのlikeレコードを取得します。
  4. モデルから新しいレコードをDBに挿入するには、まず新しいインスタンス(記入用紙)を準備して、save()メソッドで実行します。いいねの新規レコードをDBに挿入する準備として、newキーワードでLikeクラスのインスタンスを生成しておきます。参考→公式ドキュメント「クラスの基礎」
  5. 投稿テーブルを管理するReviewモデルに対してwithCountメソッドを使用することでリレーションされている別テーブルの数をカウントすることができます。今回はパラメータにいいねテーブルとのリレーション名likesを渡すことで、いいねの数を数えます。投稿idをキーにしています。likes_countはwithCountメソッドとセットの関係にあります。Laravelでは、withCount('hoge')とすると自動的にhoge_countというフィールドが生成されるのです。参考→関連するモデルのカウント
  6. jQueryの.done(data)に$paramという変数名で最新の総いいね数を返します。

Controller コントローラ(いいね数をindexに表示する)

いいねをする、撤回するの機能は以上ですが、次回index.blade.phpに訪問したときに、いいね数を返してあげないといけません。

ReviewController.php
public function index(Request $request)
{
    $items = Review::withCount('likes')->orderBy('id', 'desc')->paginate(10);
    $param = [
        'items' => $items,
    ];
    return view('reviews.index', $param);
}
  • withCount()は先ほど説明した通りです。これをitemsでビューに返して、ビューは{{$items->likes_count}}で投稿ごとにいいね数を取得、表示することができます。
  • orderBy()は指定した順番でレコードを取得するメソッド。第一パラメータは並べ替えの基準となるフィールドを選択、第二パラメータはソートする方法(昇順asc=小さい順、降順desc=大きい順)を指定します。
  • paginateはレコードが多い場合に、1ページごとの表示数を指定するメソッドです。

実装は以上になります。

エラーと対処の例

私が直面したエラーを参考にメモしておきます。

responseJSON: {message: "CSRF token mismatch.", responseText: "{↵ "message": "CSRF token mismatch.",

csrftokenmismatch.png
<meta name="csrf-token" content="{{ csrf_token() }}">をビューのheadタグに追記して解決

responseJSON: {message: "Unauthenticated."} status: 401 statusText: "Unauthorized"

unauthenticated.png
原因:ゲストのまま一生懸命いいねボタン押していたから(汗)

responseJSON: message:"Property [likes_count] does not exist on the Eloquent builder instance."

スクリーンショット 2020-10-26 21.12.13.png
→なんだったっけ...すみません。忘れました。思い出したら追記します。

あとがき

自分が実装したことをきちんと理解したく、あやふやなところを全て言語化しました。もしご指摘などあれば、ぜひコメントください。

Thanks to:
【jQuery】$(this)の意味&使い方について

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

laravel migration失敗した話 

migrationに失敗してエラーが出たので、解決策を共有します。

やりたかったことは、usersテーブルの追加です。
なので、下記コマンドを実行しました。

php artisan make:migration create_users_table
//users table 作成

エラー内容

どうやら、未定義の定数だそうです。

 ErrorException 
  Use of undefined constant JSON_INVALID_UTF8_SUBSTITUTE - assumed 'JSON_INVALID_UTF8_SUBSTITUTE'

ネットで検索してみると、PHPのバージョンに関する問題でした。
PHP7.2以降じゃないと出るエラーだとか
バージョンを確認するとPHP7.1でした。

そもそも、dockerコンテナ外でmigrationしてました。
ってことで、dockerコンテナの中へ

PHPのバージョンは7.4でした

そして、
migrationも無事通りました!!

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

Laravelでmigrationファイル作る時、複数形にしていいの?だめなの?

Laravelでmigrationファイルを作るとき、デフォルトの設定では「スネークケースで複数形で書くと、キャメルケースで単数形に変換される」ってなってる。
例えば
・create_users_table.phpという名前でmigrationファイル作成→usersというテーブル作成、modelはUsers.phpで作成する。
・create_user_books_table.phpという名前でmigrationファイル作成→user_booksというテーブルを作成、modelはuserBooks.phpで作成する。

じゃあ、「data」ってテーブル作りたいとき、どうなるの?dataって、複数形ないよね?「information」は?「news」なんて、元々複数形だけど?

もちろん$tableとかを使って自力で明示的に名前つければ悩むことはないんだけど、せっかくだったら自動的に解釈してほしいじゃないさ。

そんな時、「この単語って複数形で設定するの?」というのを確認する方法がありました。ありがとうございますLaravel の 複数形を事前に調べる

artisanでtinkerを立ち上げて、「echo Str::plural('調べたい単語');」ってすると、複数形か単数形のままか表示してくれる。

> php artisan tinker

ってやってtinker立ち上げて、

>>> echo Str::plural('book');

って打ってenterすると、

books

と返ってきます。

ちなみに
data → data
information → information
news → news

これでもう迷わない!

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

DockerでLaravel開発環境構築(Mac)

■構成

docker-laravel
├ docker-compose.yml
├ docker
│  ├ php
│  │  ├ php.ini
│  │  └ Dockerfile
│  └ nginx
│    └ default.conf
└ server

■手順

1.Dockerのデスクトップアプリをダウンロード

公式サイトより手順に従いダウンロード(簡単です)
https://www.docker.com/products/docker-desktop

2.Docker.appを起動

terminal
open /Applications/Docker.app

3.ディレクトリ作成

docker-laravelという作業用フォルダ(名称は任意)をデスクトップに用意し、
その中に上記構成でフォルダ、ファイルを配置します。
※面倒であれば以下より、git cloneを。
https://github.com/masayan1126/docker-laravel

4.主要ファイル

詳細は割愛します(githubを参照ください。)
・docker-compose.yml
・Dockerfile
・php.ini
・default.conf(nginx設定ファイル)

5.Dockerを起動してlaravelプロジェクトを作成

・dockerを起動(少し時間かかります)

docker-compose.ymlがあるディレクトリで実行
$ docker-compose up -d

・phpコンテナに入る

$ docker-compose exec php bash

・Laravelプロジェクト作成

・下記コマンド後にserverディレクトリ以下にLaravelプロジェクトが構築される。

root@ae5eec7aea3c:/var/www#
$ composer create-project laravel/laravel docker-laravel-app(プロジェクト名)

・localhostへアクセスし、laravelのトップ画面を確認できれば成功。

スクリーンショット 2020-11-08 16.20.51.png

6.ついでにmysqlも

・mysqlコンテナに入り、ログインできれば接続完了。

terminal
$ docker exec -it db-host-practice bash
terminal
mysql -u docker -p
Enter password: パスワード

・.envファイルを修正(docker-compose.ymlの内容に合わせる)

.env
DB_CONNECTION=mysql
DB_HOST=db-host-practice
DB_PORT=3306
DB_DATABASE=[docker-composeで定義したデータベース名]
DB_USERNAME=[docker-composeで定義したユーザ名]
DB_PASSWORD=[docker-composeで定義したパスワード名]

・マイグレーション

root@4ef799d9efc8:/var/www/docker-laravel-app#
php artisan migrate

※うまくいかなければ、以下を試す。
・php artisan cache:clear

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

laravel-enum:v2の日本語化

laravel-enumのv2の日本語化の書き方が他の記事と違ったので備忘録的に書き残す

環境


% sw_vers
ProductName:    Mac OS X
ProductVersion: 10.15.7
BuildVersion:   19H2

% docker version
Client: Docker Engine - Community
 Cloud integration: 1.0.1
 Version:           19.03.13
 API version:       1.40

# php -v
PHP 7.4.7 (cli) (built: Jun 11 2020 18:41:17) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Xdebug v2.9.6, Copyright (c) 2002-2020, by Derick Rethans

# php artisan -v     
Laravel Framework 7.26.1

# mysql --version
mysql  Ver 14.14 Distrib 5.7.30, for Linux (x86_64) using  EditLine wrapper

composer require bensampo/laravel-enum:2.2

最初何も考えずにcomposer require bensampo/laravel-enum叩いたら「最新のlaravel-enumはlaravel7.に対応してない」と怒られた
公式docによるとlaravel7.
に対応してるのはbensampo/laravel-enum:2.2らしいのでバージョン指定でcomposer requireする
公式doc読むの大事ほんと

composer require bensampo/laravel-enum:2.2

ここからはv2のdocを参照する

enum定義

enumファイル作成

php artisan make:enum UserType

enum定義

UserType.php
use BenSampo\Enum\Enum;
use BenSampo\Enum\Contracts\LocalizedEnum;

final class UserType extends Enum implements LocalizedEnum
{
    const Administrator = 1;
    const SuperAdministrator = 2;
}

ちなみにmysqlのenum型は0を''として予約されているので1から定義する

日本語化

多言語化のインターフェースを有効にする

# php artisan vendor:publish --provider="BenSampo\Enum\EnumServiceProvider"
Copied Directory [/vendor/bensampo/laravel-enum/resources/lang] To [/resources/lang/vendor/laravelEnum]
Publishing complete.

resouces/ja配下にenums.phpを作成して定義

resources/lang/ja/enums.php
<?php

use App\Enums\UserType;

return [

    UserType::class => [
        UserType::Administrator => '管理者',
        UserType::SuperAdministrator => 'スーパー管理者',
    ],

];

これで日本語化されます

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

【PHPの名前空間について】

version5.4以降に使える名前空間についてメモっとく

namespace A;
class A{
    public  static function xxx(){}
}
namespace B;
class A{
    public  static function yyy(){}
}

この場合namespaceAのAクラスと、namespaceBのAクラスということになる。
呼び出すときはuseキーワードを使う。

use A\A

この場合、これ以下でAクラスを呼び出したときはnamespaceAのAクラスのことを参照していることになる。

use A\B

こうすることで、namespaceAのBクラスを呼び出していることになる。

別名で呼び出す場合
use A\A as C
こうするとこれ以下で、C::xxx();としたとき、実施にはnamaspaceAのAクラスを参照していることになる。

use A\A as C
C::xxx(); 
//A::xxx();と同義

【namespaceを使う際はグローバルクラスなのかそのnamespace内のクラスなのかを意識する】
例えば、以下だとエラーになる。

namespace A
try{
    //エラー構文
}catch(Exception $e){
    echo $e->getMessage();
}

なぜなら、ここでいうExceptionクラスはnamespaceA内のものを指しているから。

\Exception 

こうしないとエラーメッセージの入ったグローバルなExceptionクラスにアクセスしていないことになる。

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

【PHP】timestamp形式の日付時刻をフォーマットして表示したい

やりたいこと

現在作成中のアプリで記事投稿日の表示フォーマットを変更したい
image.png

投稿一覧画面のソース抜粋

    <!-- 投稿一覧画面 -->
    <div class="container">
        <div class="contents">
        <?php foreach($posts_arr as $key => $value) :?>

            <div class="card" style="margin-top: 16px;">
                <div class="card-header" style="padding: 0; font-family: serif;">
                    <div class="text-center" style="margin-top: 0.6rem;  line-height: 0rem;">
                        <a><i class="fas fa-fish"></i> <?php echo $posts_arr[$key]['fish_kind']; ?></a>
                    </div>
                    <div class="text-right" style="margin-right: 0.6rem;">
                        <a style="font-size: 0.6rem;">投稿者 <span style="font-size: 0.8rem; font-weight: bold;"><?php echo $posts_arr[$key]['nickname']; ?></span>さん</span></a>
                    </div>
                </div>
                <div class="card-main" style="object-fit: contain; margin-bottom: 16px;">
                    <img src="./img/<?php echo $posts_arr[$key]['file_name']; ?>" class="card-img">
                </div>
                <div class="card-body">
                    <p class="card-text" style="margin-bottom: 0.4rem;"><?php echo mb_strimwidth($posts_arr[$key]['angler_comment'], 0, 200, '…', 'UTF-8'); ?></p>
                    <div class="text-right">
                        <a style="font-size: small;">投稿日: <?php echo $posts_arr[$key]['created_at']; ?></a>
                    </div>
                    <div class="text-right">
                        <a href="./posts/show.php?p_id=<?php echo $posts_arr[$key]['id']; ?>" class="card-link" style="font-size: small;">詳細を見る</a>
                    </div>
                </div>
            </div>

        <?php endforeach; ?>
        </div>
    </div>

改修内容

投稿日: 2020-11-05 21:42:08
表示の秒数部分を無くして
投稿日: 2020-11-05 21:42
にしたい

改修方法

 // 日付表示部分
 <div class="text-right">
     <?php $date = new DateTime($posts_arr[$key]['created_at']);?>
     <a style="font-size: small;">投稿日: <?php echo $date->format('Y-m-d H:i'); ?></a>
 </div>

関数一つ通せばできるものかなと考えながら調べている内にdate_format()を発見

しかし、date_format()はそもそもオブジェクトありきの関数だったため
フォーマットする前にオブジェクトを生成してから実行する必要があると知った。
結論、echoの前にtimestamp形式のデータをオブジェクト生成してからフォーマットする流れでできた。



<?php echo date_format($date, 'Y-m-d H:i'); ?>

もちろんechoの部分はこれでもいけますがなるべくオブジェクト指向の記述で書きたいので
前者の方を採用しました。

image.png

や↑ったぜ

参考

PHP公式リファレンス

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

array_splice()ってなんやねん!!

array_splice()ってなんやねん

説明

array_shift() - 配列の先頭の要素を取り除く
ただそれだけ、

$tehai_predict = [1,2,3,4,5,6,7,8,9,11,12,13];
$lead_element = array_shift($tehai12_predict);
var_dump($tehai_predict);
var_dump($lead_element);
[2,3,4,5,6,7,8,9,11,12,13]
1

が出力される。

筆者

2020年1月ごろからプログラミング学習し始めました。
日々の学習についてあげています。
麻雀点数計算アプリを作成中!!!

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

array_shift()ってなんやねん!!

array_shift()ってなんやねん

説明

array_shift() - 配列の先頭の要素を取り除く
ただそれだけ、

$tehai_predict = [1,2,3,4,5,6,7,8,9,11,12,13];
$lead_element = array_shift($tehai12_predict);
var_dump($tehai_predict);
var_dump($lead_element);
[2,3,4,5,6,7,8,9,11,12,13]
1

が出力される。

筆者

2020年1月ごろからプログラミング学習し始めました。
日々の学習についてあげています。
麻雀点数計算アプリを作成中!!!

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

Laravel ローカルに画像をアップロードし表示する

ode# 目次

  • Macのローカルで作成したLaravelアプリで画像ファイルをアプリのローカルにアップロードし当該画像を表示する方法をまとめる

実施環境

  • ハードウェア環境
項目 情報
OS macOS Catalina(10.15.5)
ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)
プロセッサ 2 GHz クアッドコアIntel Core i5
メモリ 32 GB 3733 MHz LPDDR4
グラフィックス Intel Iris Plus Graphics 1536 MB
  • ソフトウェア環境
項目 情報 備考
PHP バージョン 7.4.8 Homebrewを用いてこちらの方法で導入→Mac HomebrewでPHPをインストールする
Laravel バージョン 6.X commposerを用いてこちらの方法で導入→Mac Laravelの環境構築を行う
MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いてこちらの方法で導入→Mac HomebrewでMySQLをインストールする

前提条件

前提情報

  • 本記事はMacのローカルで作成されたLaravelアプリからアプリ名ディレクトリ/storage/app/public/imagesにファイルをアップロードする機能を作る。
  • アプリ名ディレクトリ/storage/app/public/imagesにアップロードされたファイルを表示するページと画像の詳細ページと画像表示ページを作成する。下記に作成するページのURLを記載する。(画像表示ページとは画像をブラウザ内の全画面で表示するページのことを指す)
    • 一覧ページののURL/output
    • 詳細ページのURL/detail/imagesテーブルのid
    • 表示ページのURL/display/imagesテーブルのid
  • 本記事の作業完了したソースは下記にアップしてある。

概要

  1. ルーティングファイルの記載
  2. コントローラファイルの記載
  3. ビューファイルの作成と記載
  4. 確認

詳細

  • 下記の説明で実行するコマンドはすべてMacのローカルのターミナルで実行するものとし、実行場所はアプリ名ディレクトリであるlaravel6_imageディレクトリとする。
  1. ルーティングファイルの記載(本作業完了後のソースコードはこちら→https://github.com/miriwo0104/laravel6_image/tree/image_display_local/01_route

    1. 下記コマンドを実行してルーティングファイルを開く。 terminal $ vi routes/web.php
    2. 下記のルーティング情報を記載する。

      laravel6_image/routes/web.php
      // 画像一覧ページ用
      Route::get('/output', 'ImageController@output')->name('output');
      
      // 画像詳細ページ用
      Route::get('/detail/{images_id}', 'ImageController@detail')->name('detail');
      
      // 画像表示ページ用
      Route::get('/display/{image_id}', 'ImageController@display')->name('display');
      
    3. 記載後のルーティングファイルの全内容を下記に記載する。

      laravel6_image/routes/web.php
      <?php
      
      /*
      |--------------------------------------------------------------------------
      | Web Routes
      |--------------------------------------------------------------------------
      |
      | Here is where you can register web routes for your application. These
      | routes are loaded by the RouteServiceProvider within a group which
      | contains the "web" middleware group. Now create something great!
      |
      */
      
      Route::get('/', function () {
          return view('welcome');
      });
      // 画像アップロードページ用
      Route::get('/input', 'ImageController@input')->name('input');
      
      // 画像アップロード処理用
      Route::post('/upload', 'ImageController@upload')->name('upload');
      
      // 下記を追記する
      // 画像一覧ページ用
      Route::get('/output', 'ImageController@output')->name('output');
      
      // 画像詳細ページ用
      Route::get('/detail/{images_id}', 'ImageController@detail')->name('detail');
      
      // 画像表示ページ用
      Route::get('/display/{image_id}', 'ImageController@display')->name('display');
      // 上記までを追記する
      
  2. コントローラファイルの記載(本作業完了後のソースコードはこちら→https://github.com/miriwo0104/laravel6_image/tree/image_display_local/02_controller

    1. 下記コマンドを実行して作成したコントローラファイルを開く。

      $ vi app/Http/Controllers/ImageController.php 
      
    2. 下記のようにコントローラファイルの内容を追記修正する。追記修正後のコントローラファイルの全体の内容を下記に記載する。

      laravel6_image/app/Http/Controllers/ImageController.php
      <?php
      
      namespace App\Http\Controllers;
      
      use Illuminate\Http\Request;
      use App\Models\Image;
      use Illuminate\Support\Facades\Storage;
      
      class ImageController extends Controller
      {
          public function input()
          {
              return view('images/input');
          }
      
          public function upload(Request $request)
          {
      
              $this->validate($request, [
                  'file' => [
                      // 空でないこと
                      'required',
                      // アップロードされたファイルであること
                      'file',
                      // 画像ファイルであること
                      'image',
                      // MIMEタイプを指定
                      'mimes:jpeg,png',
                  ]
              ]);
      
              if ($request->file('file')->isValid([])) {
      
                  $file_name = $request->file('file')->getClientOriginalName(); // オリジナルファイルのファイル名の取得
                  $file_path = Storage::putFile('/images', $request->file('file'), 'public'); // ファイルのアップロードとアップロードパスの取得
      
                  $image_info = new Image();
                  $image_info->file_path = $file_path;
                  $image_info->file_name = $file_name;
                  $image_info->save();
      
                  return redirect('/');
              }else{
                  return redirect(route('input'));
              }
      
          }
      
          // 下記を追記する
          public function output()
          {
              $image_infos = Image::select('*')->get();
              return view('images.output', ['image_infos' => $image_infos]);
          }
      
          public function detail($image_id)
          {
              $image_info = Image::find($image_id);
              return view('images.detail', ['image_info' => $image_info]);
          }
      
          public function display($image_id)
          {
              $image_info = Image::find($image_id);
              return view('images.display', ['image_info' => $image_info]);
          }
          // 上記までを追記する
      }
      
  3. ビューファイルの作成と記載(本作業完了後のソースコードはこちら→https://github.com/miriwo0104/laravel6_image/tree/image_display_local/03_view

    1. 下記コマンドを実行してビューファイルを作成して開く。

      $ vi resources/views/images/output.blade.php
      
    2. 下記の内容をビューファイルに記載する。

      laravel6_image/resources/views/images/input.blade.php
      <h1>アップロードされた画像一覧</h1>
      
      @foreach ($image_infos as $image_info)
          <hr>
          <img src="{{asset('storage/' . $image_info['file_path'])}}" alt="{{asset('storage/' . $image_info['file_path'])}}">
          <br>
          <a href="{{route('detail', ['images_id' => $image_info['id']])}}">
              <button type="submit">詳細</button>
          </a>
          <br>
          <a href="{{route('display', ['images_id' => $image_info['id']])}}">
              <button type="submit">表示</button>
          </a>
      @endforeach
      
    3. 下記コマンドを実行してビューファイルを作成して開く。

      $ vi resources/views/images/detail.blade.php
      
    4. 下記の内容をビューファイルに記載する。

      laravel6_image/resources/views/images/detail.blade.php
      <h1>画像の詳細</h1>
      
      <img src="{{asset('storage/' . $image_info['file_path'])}}" alt="{{asset('storage/' . $image_info['file_path'])}}">
      <br>
      <a href="{{route('display', ['images_id' => $image_info['id']])}}">
          <button type="submit">表示</button>
      </a>
      <ul>
          <li>ID: {{$image_info['id']}}</li>
          <li>アップロード日: {{$image_info['created_at']}}</li>
      </ul>
      
  4. 確認

    1. 下記コマンドを実行してローカルサーバを起動する。

      $ php artisan serve 
      
    2. 下記にアクセスする。

    3. 登録してある画像が表示されることを確認する。

      127_0_0_1_8000_output.png

    4. 画像下部の「詳細」をクリックする。

      127_0_0_1_8000_output-2.png

    5. 画像の詳細情報が表示されることを確認する。

      127_0_0_1_8000_detail_1.png

    6. 画像下部の「表示」をクリックする。

      127_0_0_1_8000_output-3.png

    7. 画像のみがブラウザで表示されることを確認する。

      1__882×782_.png

参考文献

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

prepare関数について

prepareとは?

prepare = SQL文の基本部分が同じで値だけ異なるような場合(例えば同じテーブルに値だけ変えて何回もデータを挿入するような場合)に効率よく行える機能を提供

prepareの他に似たメソッドとしてqueryがあります。
違いは、
query変動値がない場合に使用

$pdo->query('SELECT * FROM user');

prepare変動値がある場合に使用(prepare、bindValue、executeを使う)

$stmt = $pdo->prepare("SELECT * FROM user WHERE name=:name");

$stmt->bindValue(':name', $name, PDO::PARAM_STR);

$stmt->execute();

参考元はこちら
(https://blog.senseshare.jp/query-prepare.html#index1)
(https://www.javadrive.jp/php/pdo/index8.html)

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

Laravel MAMPの設定からmigrateまでの手順

環境

・Laravel 6.19.1
・MAMP 5.7

MAMPをインストールし、Webブラウザを立ち上げる

MAMPをインストールし、起動する。
MAMPインストールページ

起動後、「⌘ + ,」を入力し、Preferenceを立ち上げる。
Portsタブを選択し、「Apache Port」と「Nginx Port」を80、「MySQL Port」を3306に変更する。
image.png
OKを選択し、次はMAMPの「Start Servers」をクリックする。
image.png
サーバーが立ち上がると、MAMPのWebページトップに遷移する。
image.png
「TOOLS」タブを選択し、「PHPMYADMIN」を選択する。

データーベース設定を行う

ここから本題のデーターベース設定に移っていく。
まずはデフォルトでは英語の設定になっているので、日本語に設定を行う。
image.png
次に左のサイドメニューから「新規作成」をクリックし、データーベースを作成していく。
image.png
データーベース名を入力し、「作成」をクリックすると、先ほど入力したデーターベース名で新規作成される。

データベース ユーザーの作成

先程作成したデーターベースを選択し、タブの「特権」を選択する。
下に「ユーザーアカウントを追加する」とあるので、クリックする。
image.png
ログイン情報の設定を行う画面に遷移するので、「ユーザー名」と「パスワード」を任意で設定し、
ホスト名を「%」→「localhost」に変更する。
image.png
ページの下の方にいくと、「Check all」とあるので、チェックを入れる。
image.png
右下の「実行」をクリックすると、ユーザーが新規作成される。
image.png
改めて新規作成したデータベースを選択 -> 「特権」をクリックし、作成したユーザーが表示されていればOK。
MySQL側の設定はこれで完了。

Laravel データベース設定

VSCodeなどのエディタを使い、環境設定ファイル(.envファイル)を開く。
image.png
画像に基づき、必要な情報を入力していく。

database.phpの設定。

次に、データベース設定ファイル( config > database.php )を開く。
46行目あたりから、mysqlの設定が記載されているので移動してください。

'mysql' => [
    'driver' => 'mysql',
    'host' => env('DB_HOST', '127.0.0.1'),
    'port' => env('DB_PORT', '3306'),
    'database' => env('DB_DATABASE', 'forge'),
    'username' => env('DB_USERNAME', 'forge'),
    'password' => env('DB_PASSWORD', ''),
    'unix_socket' => env('DB_SOCKET', ''),
    'charset' => 'utf8mb4',
    'collation' => 'utf8mb4_unicode_ci',
    'prefix' => '',
    'strict' => true,
    'engine' => null,
],

デフォルトでは'unix_socket' => env('DB_SOCKET', ''),となっているのでここを

'unix_socket' => '/Applications/MAMP/tmp/mysql/mysql.sock',

に変更する。
この設定を行わないと、$ php artisan migrateコマンドを入力した際にエラーが発生する。(下記参照)

[PDOException]SQLSTATE[HY000] [2002] No such file or directory

php artisan migrate 実行

ここまでの手順を踏み、ターミナルでmigrateコマンドを入力する。

$ php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_18_19_000000_create_failed_jobs_table

このような表示がされれば上手くいっています。
念のため、MAMPのWebページに戻り、新規作成したデーターベースを確認してみましょう。
image.png
このように、テーブルが3つ作成されていれば問題ないです。
今回は以上になります。

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

Laravel MAMPの設定からmigrateまでの手順 [忘備録]

環境

・PHP 7.4.9
・Laravel 6.19.1
・MAMP 5.7

MAMPをインストールし、Webブラウザを立ち上げる

MAMPをインストールし、起動する。
MAMPインストールページ

起動後、「⌘ + ,」を入力し、Preferenceを立ち上げる。
Portsタブを選択し、「Apache Port」と「Nginx Port」を80、「MySQL Port」を3306に変更する。
image.png
OKを選択し、次はMAMPの「Start Servers」をクリックする。
image.png
サーバーが立ち上がると、MAMPのWebページトップに遷移する。
image.png
「TOOLS」タブを選択し、「PHPMYADMIN」を選択する。

データーベース設定を行う

ここから本題のデーターベース設定に移っていく。
まずはデフォルトでは英語の設定になっているので、日本語に設定を行う。
image.png
次に左のサイドメニューから「新規作成」をクリックし、データーベースを作成していく。
image.png
データーベース名を入力し、「作成」をクリックする。
入力したデーターベース名で、左サイドメニューに新規作成されていれば上手くいってます。
次はデーターベースユーザーの作成を行っていきます。

データベース ユーザーの作成

先程作成したデーターベースを選択し、画面右上に表示されているタブの「特権」を選択する。
下に「ユーザーアカウントを追加する」とあるので、クリックする。
image.png
ログイン情報の設定を行う画面に遷移するので、「ユーザー名」と「パスワード」を任意で設定し、
ホスト名を「%」→「localhost」に変更する。
image.png
ページの下の方にいくと、「Check all」とあるので、チェックを入れる。
image.png
右下の「実行」をクリックすると、ユーザーが新規作成される。
image.png
改めて新規作成したデータベースを選択 -> 「特権」をクリックし、作成したユーザーが表示されていればOK。
MySQL側の設定はこれで完了。

Laravel データベース設定

VSCodeなどのエディタを使い、環境設定ファイル(.envファイル)を開く。
image.png
画像に基づき、必要な情報を入力していく。(下記参考例)

DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=test
DB_USERNAME=test_user
DB_PASSWORD=test12345

database.phpの設定。

次に、データベース設定ファイル( config > database.php )を開く。
46行目あたりから、mysqlの設定が記載されているので移動してください。

'mysql' => [
    'driver' => 'mysql',
    'host' => env('DB_HOST', '127.0.0.1'),
    'port' => env('DB_PORT', '3306'),
    'database' => env('DB_DATABASE', 'forge'),
    'username' => env('DB_USERNAME', 'forge'),
    'password' => env('DB_PASSWORD', ''),
    'unix_socket' => env('DB_SOCKET', ''),
    'charset' => 'utf8mb4',
    'collation' => 'utf8mb4_unicode_ci',
    'prefix' => '',
    'strict' => true,
    'engine' => null,
],

デフォルトでは'unix_socket' => env('DB_SOCKET', ''),となっているのでここを

'unix_socket' => '/Applications/MAMP/tmp/mysql/mysql.sock',

に変更する。
この設定を行わないと、$ php artisan migrateコマンドを入力した際にエラーが発生する。(下記参照)

[PDOException]SQLSTATE[HY000] [2002] No such file or directory

php artisan migrate 実行

ここまでの手順を踏み、ターミナルでmigrateコマンドを入力する。

$ php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_18_19_000000_create_failed_jobs_table

このような表示がされれば上手くいっています。
念のため、MAMPのWebページに戻り、新規作成したデーターベースを確認してみましょう。
image.png
このように、テーブルが3つ作成されていれば問題ないです。
今回は以上になります。

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

サニタイズ / エスケープについて

サニタイズとは?

サニタイズ = 危険なコードやデータを変換または除去して無力化する処理。

例えば、Webサイトに設置された入力フォームなどから、悪意のあるコードが入力され、その文字列が実行されることで様々な被害に遭う可能性があります。

サニタイズの一種にエスケープというものがあり、
大抵はサニタイズエスケープとして捉えている事がほとんどです。

エスケープとは?

エスケープ = 特殊な文字を無害な文字に強制的に置き換える処理。
PHPでは< > ' " &などの文字を変数値としてそのまま使用することができますが、これをHTMLにそのまま出力すると、HTMLタグなどの特殊文字として解釈されてしまいます。
なのでエスケープ処理することにより被害防止をすることが出来ます。

例えばHTMLだと、エスケープ処理することで下記のように勝手に特殊文字を変換してくれます。

表示 エスケープ
> &lt
< &gt
" &quot
' &#039
<?php

$html = '<h2>エスケープについて</h2>';

?>
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>サニタイズについて</title>
</head>
<body>
<h1>サニタイズについて</h1>
<?php echo $html; ?>
</body>
</html>

↓ ブラウザ表示
サニタイズについて //HTMLは問題なし。
<h2>エスケープについて</h2> // PHPは <h2> も表示されてしまう。

これではJavaScriptなどで悪質なコードを入力されてしまうと、勝手にアラートを鳴らされたり、ブラウザの中にある個人情報を勝手に取得して外部に送信されてしまうなどの恐れが出てきます。

対策方法は、例えばPHPには専用関数でhtmlspecialchars()があります。
JavaScriptやSQLでの対策方法については参考元リンクよりご覧ください。

参考元はこちら
エスケープ(https://qiita.com/n_hirai/items/df0a21d2409ee47973e5)
対策方法(https://webukatu.com/wordpress/blog/1635/#i)

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