20200619のlaravelに関する記事は11件です。

element-uiのvalidatorの使い方

概要

laravel+vueで作ったWebサイトでバリデーション処理を作ったのでメモ。

formのattributeを使ったバリデーション

el-formとel-form-itemの属性を使ってバリデーションを組む。
コード量が少ないがテーブルのように複数アイテムがある場合や、APIを叩いてチェックしたい場合など静的ルールに表現できない場合は使えない。

<el-form ref="formData" :model="formData" label-position="top">
  <el-form-item :rules="rules" :prop="propname">
    ・・・
  </el-form-item>
</el-form>

(略)
// バリデーション実行
const fm: any = _this.$refs["formData"];
fm.validate((valid, fields) => {
  // バリデーションNGの場合は処理中断
  if (valid === false) {
    // 処理完了通知
    _this.$message({
      message: "入力内容が正しくない項目があります、赤色のエラー部分をご確認ください。",
      type: "warning",
      duration: 0,
      showClose: true
    });
    return;
  }

  //バリデーションOKの処理
  ・・
}

formのvalidateメソッドをコールするとコールバックで結果が返ってくる。
validがbooleanのバリデーションOK/NG、fieldsにNG時の詳細(フィールド名とルールで指定したエラーメッセージのセット)が入ってくる。

propnameにチェック対象のデータ変数を指定する。
rulesがバリデーションルール。以下にいくつかの例。

必須チェック

private rules: Object[] = [];

let requiredRule = {
  required: true,
  message: "入力必須です。",
  trigger: "blur"
};
this.rules.push(requiredRule);

郵便番号

let zipRules = {
  pattern: /^[0-9]{3}-[0-9]{4}$/,
  message: "正しい形式で入力してください。 例)123-4567",
  trigger: "blur"
};
this.rules.push(zipRules);

電話番号・FAX番号

let telFaxRules = {
  pattern: /^[-0-9]*$/,
  message: "正しい形式で入力してください。 例)1234-56-7890",
  trigger: "blur"
};
this.rules.push(telFaxRules);

e-Mail

let emailRules = {
  type: "email",
  message: "メールアドレス形式で入力してください。",
  trigger: "blur"
};
this.rules.push(emailRules);

全角チェック

// 英字
if (_this.charPattern.indexOf(CharPatternEnum.Alphabet) >= 0) {
  charPatternNames.push("英字");
  regExpPattern += "a-zA-Z";
}

// 数字
if (_this.charPattern.indexOf(CharPatternEnum.Numeric) >= 0) {
  charPatternNames.push("数字");
  regExpPattern += "0-9";
}

// カタカナ
if (_this.charPattern.indexOf(CharPatternEnum.Katakana) >= 0) {
  charPatternNames.push("カタカナ");
  regExpPattern += "ァ-ヶー";
}

// スペース
if (_this.charPattern.indexOf(CharPatternEnum.Space) >= 0) {
  charPatternNames.push("スペース");
  regExpPattern += " ";
}

// ハイフン
if (_this.charPattern.indexOf(CharPatternEnum.Hyphen) >= 0) {
  charPatternNames.push("ハイフン( ‐ )");
  regExpPattern += "‐";
}

// 全角以外の定義が無い場合の処理
if (charPatternNames.length === 0) {
  regExpPattern = "^\x00-\xFFァ-ンヲー゚゙";
}

// 正規表現パターン終端処理
regExpPattern = "^[" + regExpPattern + "]*$";

// メッセージ作成
if (charPatternNames.length >= 2) {
  charPatternMsg += "の";
}
charPatternMsg += charPatternNames.join("・") + "で入力してください。";

// ルールに追加
let charPatternRules = {
  pattern: new RegExp(regExpPattern),
  message: charPatternMsg,
  trigger: "blur"
};
this.rules.push(charPatternRules);

半角チェック

// 半角の場合
charPatternMsg += "半角";

// 英字
if (_this.charPattern.indexOf(CharPatternEnum.Alphabet) >= 0) {
  charPatternNames.push("英字");
  regExpPattern += "a-zA-Z";
}

// 数字
if (_this.charPattern.indexOf(CharPatternEnum.Numeric) >= 0) {
  charPatternNames.push("数字");
  regExpPattern += "0-9";
}

// カタカナ
if (_this.charPattern.indexOf(CharPatternEnum.Katakana) >= 0) {
  charPatternNames.push("カタカナ");
  regExpPattern += "ァ-ンヲー゚゙";
}

// 記号
if (_this.charPattern.indexOf(CharPatternEnum.Symbol) >= 0) {
  charPatternNames.push("記号");
  // todo: とりあえず全ての記号を網羅したが、使わなそうな記号は省いた方が良いかも。。@2020/01/28 s.aono
  regExpPattern += "\x20-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E";
}

// スペース
if (_this.charPattern.indexOf(CharPatternEnum.Space) >= 0) {
  charPatternNames.push("スペース");
  regExpPattern += " ";
}

// ハイフン
if (_this.charPattern.indexOf(CharPatternEnum.Hyphen) >= 0) {
  charPatternNames.push("ハイフン( - )");
  regExpPattern += "\\-";
}

// 半角以外の定義が無い場合の処理
if (charPatternNames.length === 0) {
  regExpPattern = "\x20-\x7E\uFF65-\uFF9F";
}

// 正規表現パターン終端処理
regExpPattern = "^[" + regExpPattern + "]*$";

// メッセージ作成
if (charPatternNames.length >= 2) {
  charPatternMsg += "の";
}
charPatternMsg += charPatternNames.join("・") + "で入力してください。";

// ルールに追加
let charPatternRules = {
  pattern: new RegExp(regExpPattern),
  message: charPatternMsg,
  trigger: "blur"
};
this.rules.push(charPatternRules);

async-validatorを使ったカスタムバリデーション

テーブルのように複数アイテムがある場合や、APIを叩いてチェックしたい場合など動的にチェックしたい場合に使う方法。
実はel-formもasync-validatorを使っている。

this.validate(function(valid: boolean) {
  // バリデーションNGの場合は処理中断
  if (!valid) {
    // 処理完了通知
    _this.$message({
      message: "入力内容が正しくない項目があります、赤色のエラー部分をご確認ください。",
      type: "warning",
      duration: 0,
      showClose: true
    });
    return;
  }

  ここにバリデーションOK時の処理
}

//バリデーション実体
validate(callback) {
  const _this = this;
  _this.serviceTypeMstData.forEach(row => {
    _this.checkInput(row, [
      "service_type_code",
      "service_item_code",
      "service_type_display_name"
    ]);
  });
  var ret = true;
  _this.serviceTypeMstData.forEach(row => {
    if (row.isValidateError) {
      ret = false;
    }
  });
  callback(ret);
}

//必須項目の入力チェック
checkInput(row: ServiceTypeMasterExData, targets: string[]) {
  const _this = this;

  var rules = {};
  targets.forEach((value: any, index: any) => {
    rules = Object.assign(rules, this.getRule(value));
  });

  var validator = new schema(rules);
  validator.validate(this.getValidateObject(row), (errors, fields) => {
    if (errors && !row.isAddButton) {
      // バリデーションエラー時
      errors.forEach((value: any, index: any) => {
        row.validate_error[value["field"]] = value["message"];
      });
    } else {
      targets.forEach((value: any, index: any) => {
        row.validate_error[value] = "";
      });
    }
    row.isValidateError = (function(row) {
      for (var key in row.validate_error) {
        if (row.validate_error[key] != "") {
          return true;
        }
      }
      return false;
    })(row);
  });
}

//対象に絞ったバリデーションルール取得
getRule(name: string) {
  return {
    service_type_code: { service_type_code: [{ required: true, message: "入力必須です。" }] },
    service_item_code: { service_item_code: [{ required: true, message: "入力必須です。" }] },
    service_type_display_name: {
      service_type_display_name: [{ required: true, message: "入力必須です。" }]
    }
  }[name];
}

//バリデーション対象オブジェクト作成
getValidateObject(row: ServiceTypeMasterExData): object {
  return {
    service_type_code: row.service_type_code,
    service_item_code: row.service_item_code,
    service_type_display_name: row.service_type_display_name
  };
}

静的ルールならel-formの時に使ったルールで記述できる。
動的ルールならcheckInputで判定処理を入れて結果をerrorsにbooleanで返し、fieldsに詳細をフィールド名(キー名:field)とメッセージ(キー名:message)で返せばいい。

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

el-uploadを使ったファイル添付機能

概要

laravel+vueで作成したWebサイトにて添付ファイル機能を実装したのでメモ。
フロント側のUIはelement-uiのel-uploadで作った。

コード

vue側

<el-row :gutter="24" class="attachment-row">
  <el-col :span="24">
    <span class="item-label"><i class="fas fa-paperclip icon"></i>添付ファイル</span>
    <el-upload
      action="#"
      list-type="text"
      class="attachments-upload"
      :before-remove="beforeAttachmentsRemove"
      :on-change="addAttachments"
      :file-list="attachmentsList"
      :auto-upload="true"
      :show-file-list="true"
      :multiple="false"
      :drag="true"
      :limit="attachments_limit"
      :http-request="AttachmentsHttpRequest"
    >
      <i class="el-icon-upload"></i>
      <div class="el-upload__text">
        ここにドラッグするか <em>ここをクリックして</em>アップロードしてください。
      </div>
      <div slot="file" slot-scope="{ file }">
        <div class="contents" :class="{ delete: file.isDelete }">
          <span v-if="file.isDelete" class="el-upload-list__item-deleted">
            <span class="el-upload-list__item-label">
              <span class="each-row">削除は更新実行後に反映されます。</span>
            </span>
          </span>
          <li>
            <div v-if="!file.isUploaded">
              <i class="el-icon-document icon"></i>
              <span v-if="!file.isUploaded" class="el-upload-list__item-noactions">
                <span class="el-upload-list__item-label">
                  <span class="each-row">アップロード</span>
                  <span class="each-row">待ち</span>
                </span>
              </span>
              <span class="filename">{{ file.name }}</span>
              <i
                class="el-icon-close"
                :disabled="file.isDelete"
                @click="deleteAttachments(file)"
              ></i>
            </div>
            <div v-else class="available-download">
              <i class="el-icon-document icon" @click="downloadAttachment(file)"></i>
              <span class="filename" @click="downloadAttachment(file)">{{
                file.name
              }}</span>
              <i
                class="el-icon-close close-icon"
                :disabled="file.isDelete"
                @click="deleteAttachments(file)"
              ></i>
            </div>
          </li>
        </div>
      </div>
    </el-upload>
  </el-col>
</el-row>

(略)
private attachments_limit: number = 10;
private attachmentsList: AttachementData[] = [];
private fileReader: FileReader = new FileReader();

//添付ファイル追加(AttachmentsHttpRequestより後で呼ばれる)
addAttachments(file: any, fileList: any) {
  // File size limitation
  const isLt5M = file.size / 1024 / 1024 < 100;
  if (!isLt5M) {
    this.$message.error("アップロードできるファイルサイズは100Mまでです。");
    this.deleteAttachments(file);
    return false;
  }
  // Exceed limit
  if (this.attachmentsList.length >= this.attachments_limit) {
    this.$message.error("アップロードできるファイルは" + this.attachments_limit + "つまでです。");
    this.deleteAttachments(file);
    return false;
  }

  file.id = file.uid;
  file.isUploaded = false;
  file.isDelete = false;
  this.attachmentsList.push(file);
}

//添付ファイル削除
deleteAttachments(file: any) {
  if (file.isUploaded) {
    //アップロード済み

    //画面表示リストの削除フラグ立てる
    let t = this.attachmentsList.filter(function(e) {
      return e === file;
    });
    t[0].isDelete = true;

    //サーバへ送るリストの削除フラグを立てる
    let t2 = this.minute.attachments_base64.filter(function(e) {
      return e.id == file.uid;
    });
    t2[0].isDelete = true;
  } else {
    //未アップロード

    //画面表示リストから消す
    this.attachmentsList = this.attachmentsList.filter(function(e) {
      return e !== file;
    });

    //サーバへ送るリストから消す
    this.minute.attachments_base64 = this.minute.attachments_base64.filter(function(e) {
      return e.id != file.uid;
    });
  }
}

//添付ファイル追加時のアップロード
AttachmentsHttpRequest(options: any) {
  let file = options.file;
  let filename = file.name;
  if (file) {
    this.fileReader.readAsDataURL(file);
  }
  this.fileReader.onload = () => {
    if (!this.minute.attachments_base64.length) {
      this.minute.attachments_base64 = [];
    }
    this.minute.attachments_base64.push({
      id: file.uid,
      filetype: file.type,
      original_filename: filename,
      comment: "", //アップロード時は空
      base64data: this.fileReader.result,
      isUploaded: false,
      isDelete: false
    });
  };
}

//画面初期表示時に既存添付ファイルを反映する
defaultAttachmentsListLoad(data: Attachement[]) {
  this.attachmentsList = [];
  data.forEach((value, index) => {
    if (this.attachmentsList.length < this.attachments_limit) {
      this.attachmentsList.push({
        id: value.id,
        name: value.original_filename,
        comment: value.comment,
        isUploaded: true,
        isDelete: false,
        uid: value.id,
        url: ""
      });
    }
  });
}

//添付ファイルの属性入力時にdeleteキーやBackSpaceキーでリスト削除が走らないようにするための処理
beforeAttachmentsRemove(file, fileList) {
  return false;
}

/**
 * 添付ファイルダウンロード
 */
downloadAttachment(file: any) {
  const _this = this;
  axios({
    url: "/api/minute/download/attachment",
    params: { minute_id: this.minute.id, attachment_id: file.uid },
    method: "GET",
    responseType: "blob" // これがないと文字化けする
  })
    .then(res => {
      const blob = new Blob([res.data], {
        type: res.data.type
      });

      //レスポンスヘッダからファイル名を取得します
      var fileName = res.headers["download_filename"];
      fileName = decodeURI(fileName).replace(/\+/g, " ");

      //ダウンロードします
      saveAs(blob, fileName);
    })
    .catch(error => {
      var errorMsg = "不明";

      // 通知
      _this.$message({
        message: "実行に失敗しました。[" + errorMsg + "]",
        type: "error",
        duration: 0,
        showClose: true
      });
    });
}

バックエンド側

[画面表示時のメタデータ取得]
//添付ファイル(メタ情報だけ返す)
$attachments_base64 = [];
if ($minute->attachments) {
  $attachment_paths = json_decode($minute->attachments);
  foreach ($attachment_paths as $file) {
    $attachments_base64[] = [
      'id' => $file->id,
      'original_filename' => $file->original_filename,
      'isUploaded' => true,
      'isDelete' => false
    ];
  }
}
$minute['attachments_base64'] = $attachments_base64;
[保存時]
//添付ファイルをファイルとして保存する
$attachment_json = [];
foreach ($request->attachments_base64 as $file) {
  $path = str_pad($minute->id, 10, '0', 0) . '_' . $file['id'];
  if ($file['isUploaded'] && !$file['isDelete']) {
    //アップロード済みで削除でない->何もしない
    $attachment_json[] = ['id' => $file['id'], 'path' => $path, 'original_filename' => $file['original_filename']];
  } elseif ($file['isUploaded'] && $file['isDelete']) {
    //アップロード済みで削除する->実体ファイルを削除しJSONに書き込まない
    if (Storage::disk('local')->exists('/minute/' . $path)) {
      Storage::disk('local')->delete('/minute/' . $path);
    }
  } elseif (!$file['isUploaded']) {
    //アップロード済されていない->アップロードする
    $fileData = $file['base64data'];
    if (count(explode(';', $fileData)) > 1) {
      list(, $fileData) = explode(';', $fileData);
    }
    if (count(explode(',', $fileData)) > 1) {
      list(, $fileData) = explode(',', $fileData);
    }
    $fileData = base64_decode($fileData);
    Storage::disk('local')->put('/minute/' . $path, $fileData);
    $attachment_json[] = ['id' => $file['id'], 'path' => $path, 'original_filename' => $file['original_filename']];
  }
}
$minute->attachments = json_encode($attachment_json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
$minute->save(); //DBに添付ファイルパスを更新
[ダウンロード時]
public function attachmentDownload(Request $request)
{
  $minute = Minutes:: firstOrNew(['id' => $request->minute_id]);

  //添付ファイル
  if ($minute->attachments) {
    $attachment_paths = json_decode($minute->attachments);
    foreach ($attachment_paths as $file) {
      if ($file->id == $request->attachment_id) {
        $mimeType = Storage::mimeType('/minute/' . $file->path);
        $headers = [['Content-Type' => $mimeType], 'download_filename' => urlencode($file->original_filename)];
        return Storage::response('/minute/' . $file->path, $file->original_filename, $headers, 'attachment');
      }
    }
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue CLI+ Laravel 5.8 で、チャット作成 firebase Cloud Messaging対応

概要

前の 、クロスドメイン構成の
Vue-CLI + Laravel 関連となり。チャット機能の作成です

・会員制となり、Google認証で
 ログインに対応しています。
・PWAも対応しています
・ 前のLaravel版チャットを、Vue CLIに移植した形です

構成

PWA
firebase Cloud Messaging / FCM
Notification API
Vue CLI
vue-router
vue/cli-service : 4.4.0
・API サービス:
Laravel 5.8
nginx
mysql
・フロント設置ドメイン ,Vue-CLI:
netlify / ホスティングサービス


画面

・web push 受信時の、通知API で。タスクバー表示

ss-chat-0619-noti.png

・チャット画面、詳細

ss-chat-0619-show.png

・一覧
左側に、参加チャットの表示
ss-chat-0619a.png


Vue components

・index
https://github.com/kuc-arc-f/vue_spa3a_4chat/blob/master/src/components/Chats/Index.vue

・create
https://github.com/kuc-arc-f/vue_spa3a_4chat/blob/master/src/components/Chats/new.vue

・show
https://github.com/kuc-arc-f/vue_spa3a_4chat/blob/master/src/components/Chats/show.vue

package.json

https://github.com/kuc-arc-f/vue_spa3a_4chat/blob/master/package.json


参考のページ

https://knaka0209.hatenablog.com/entry/lara58_31cross_chat

・Vue CLI+ Laravel 5.8で、クロスドメイン SPA構成/PWA対応のCRUD作成する。
https://qiita.com/knakaqi/items/1bae7a540aa13ce8233b


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

vagrant コマンドがなんだか動かない時

vagrant コマンドがよくわからないエラーに悩まされるときがある。

そんな時は、xcodeのバージョンアップが影響している事がしばしば。

一旦以下のコマンドを実行して試してみる事をオススメする。

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

laravel-dump-serverのインストールでエラー

経緯

新しく用意したLaravel開発用のサーバーにおいて、そろそろdump-serverで色々dumpさせて確認しようかと思い、下記をコマンドを叩いたら

composer require --dev beyondcode/laravel-dump-server

こんなエラーが返ってきて本気っすか?ってなった話です。

Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - Installation request for beyondcode/laravel-dump-server ^1.4 -> satisfiable by beyondcode/laravel-dump-server[1.4.0].
    - Conclusion: remove symfony/var-dumper v4.4.10
    - Conclusion: don't install symfony/var-dumper v4.4.10
    - beyondcode/laravel-dump-server 1.4.0 requires symfony/var-dumper ^5.0 -> satisfiable by symfony/var-dumper[5.0.x-dev, 5.1.x-dev, 5.2.x-dev, v5.0.0, v5.0.0-BETA1, v5.0.0-BETA2, v5.0.0-RC1, v5.0.1, v5.0.10, v5.0.2, v5.0.3, v5.0.4, v5.0.5, v5.0.6, v5.0.7, v5.0.8, v5.0.9, v5.1.0, v5.1.0-BETA1, v5.1.0-RC1, v5.1.0-RC2, v5.1.1, v5.1.2].
    - Can only install one of: symfony/var-dumper[5.0.x-dev, v4.4.10].
    - Can only install one of: symfony/var-dumper[5.1.x-dev, v4.4.10].
  〜省略〜
    - Can only install one of: symfony/var-dumper[v5.1.1, v4.4.10].
    - Can only install one of: symfony/var-dumper[v5.1.2, v4.4.10].
    - Installation request for symfony/var-dumper (locked at v4.4.10) -> satisfiable by symfony/var-dumper[v4.4.10].

サーバー環境の確認

エラーを見る感じsymfony/var-dumperが要因なのはなんとなく分かるのですが、
こっから何をどうすりゃいいのか?な状態。
改めて環境を確認すると

php -v
PHP 7.4.5 (cli) (built: Apr 23 2020 00:10:21) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.5, Copyright (c), by Zend Technologies
php artisan --version
Laravel Framework 6.18.20

割と新しい目な構成。
もっと枯れたバージョンを使うしか無いんかなと諦め気味にググると下記をしろとな。
本家でissueが発行されてたのですが、laravel-dump-serverのバージョンを指定しないとダメやでって事でした。
https://github.com/beyondcode/laravel-dump-server/issues/62

composer require --dev beyondcode/laravel-dump-server:1.3.0

俺のdump-serverが帰ってきた。

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

cygwinのdockerでエラー [Failed to execute script docker-compose]

laravelを使ってlocal開発をしている時にdockerコマンドでエラーが発生するようになった。
またローカルの環境ではcygwin上で開発を行っている。

╰─➤  docker-compose ps                                                                                                                                                                                                                                                 127 ↵
[6276] Failed to execute script docker-compose
Traceback (most recent call last):
  File "docker-compose", line 6, in <module>
  File "compose\cli\main.py", line 72, in main
  File "compose\cli\main.py", line 125, in perform_command
  File "compose\cli\command.py", line 47, in project_from_options
  File "compose\config\environment.py", line 77, in from_env_file
  File "compose\config\environment.py", line 72, in _initialize
  File "compose\config\environment.py", line 44, in env_vars_from_file
  File "c:\jenkins\workspace\dsg_compose_1.25.5\venv\lib\codecs.py", line 714, in __next__
  File "c:\jenkins\workspace\dsg_compose_1.25.5\venv\lib\codecs.py", line 645, in __next__
  File "c:\jenkins\workspace\dsg_compose_1.25.5\venv\lib\codecs.py", line 558, in readline
  File "c:\jenkins\workspace\dsg_compose_1.25.5\venv\lib\codecs.py", line 504, in read
  File "c:\jenkins\workspace\dsg_compose_1.25.5\venv\lib\encodings\utf_8_sig.py", line 117, in decode
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 10: invalid start byte

色々ググってみたが、日本語を使ってるととか情報が出てくるがそもそもdocker関係を修正していないにエラーが発生するようになった。

で思いついたのがlaravelで使用している .env をシンボリックリンクに変更したのを思い出し元に戻したら直った。。
dockerも .envを使うらしいのでcygwin環境下でのシンボリックリンクがなんかだめみたい。

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

AWS S3 Laravel 画像ファイルアップロード時にエラーが発生する

目的

  • 画像ファイルのアップロード時にpublic指定してアップロードしたところ

実施環境

  • ハードウェア環境
項目 情報
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
項目 情報 備考
AWS EC2インスタンス AmazonLinux2 こちらの方法を用いてイメージからインスタンスを作成→AWS EC2 をMacで使ってみよう!
PHP 7.4.5 こちらの方法でインストール→AWS EC2 AmazonLinux2 PHPをインストールする
composer 1.10.7 こちらの方法でインストール→AWS EC2 AmazonLinux2 composerをインストールする
MySQL 8.0.20 for Linux on x86_64 こちらの方法でインストール→AWS EC2 AmazonLinux2 MySQLを使えるようにする

問題までの経緯

  1. 下記の方法にてS3に対する画像アップロード処理を実装した。
  2. 画像アップロード時の処理を下記の様に修正してpublic状態でアップロードできる様にした。

    • 修正前

      アプリ名ディレクトリ/app/Http/Controllers/ImageController.php
       Storage::disk('s3')->putFile('/test', $request->file('file'));
      
    • 修正後

      アプリ名ディレクトリ/app/Http/Controllers/ImageController.php
       Storage::disk('s3')->putFile('/test', $request->file('file'), 'public');
      
  3. 処理の動作を確認するためブラウザから画像のアップロードを行った。

問題

  • 下記エラーが発生する。

     Error executing "PutObject" on "https://S3のバケットURL/test/8iPIwmDI2VX6qOZwUq9XCJSLPGxGox2kxpfcOAkT.png"; AWS HTTP error: Client error: `PUT https://S3のバケットURL/test/8iPIwmDI2VX6qOZwUq9XCJSLPGxGox2kxpfcOAkT.png` resulted in a `403 Forbidden` response: <?xml version="1.0" encoding="UTF-8"?> <Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>5D23B7 (truncated...) AccessDenied (client): Access Denied - <?xml version="1.0" encoding="UTF-8"?> <Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>5D23B7FF0F008DAE</RequestId><HostId>PETGLYLAydwTGLlN6wskfKQpjULz1bGIQqWZc12NaJvZfCN++WYmjIFJrVoA2V8LNUK+fQwwoJk=</HostId></Error>
    
  • ブラウザでのエラー画面の表示を下記に記載する。

    ?_Error_executing__PutObject__on__https___image-upload-miriwo_s3_ap-northeast-1_amazonaws_com_test_8iPIwmDI2VX6qOZwUq9XCJSLPGxGox2kxpfcOAkT_png___AWS_HTTP_error__Client_error__`PUT_https___image-upload-miriwo_s3_ap-northeast-1_amazonaws_co.png

問題解決までの経緯

  1. AWSのコンソールにログインしてS3のコンソールを開く。
  2. アップロードを行うバケット名をクリックする。

    S3_Management_Console.png

  3. 「アクセス権限」をクリックする。

    S3_Management_Console.png

  4. 「パブリックアクセス」の「編集」をクリックする。

    S3_Management_Console.png

  5. 各チェックを外し「保存」をクリックする。

    S3_Management_Console.png

  6. ブラウザから画像アップロードを実施したところエラーが解消され画像ファイルがアップロードされた。

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

Formファサード

Formファサードの記録

Form::open():HTMLフォームを作成する
Form::model():モデルをベースにしたフォームを作成する
Form::close():フォームを閉じる
Form::label():フォームのラベルを作成する
Form::input():入力フィールドを作成する
Form::text():テキスト入力フィールドを作成する
Form::password():パスワードフィールドを作成する
Form::hidden():hiddenフィールドを作成する
Form::number():数値フィールドを作成する
Form::email():メールアドレス入力フィールドを作成する
Form::url():URL入力フィールドを作成する
Form::file():ファイルアップロードのフィールドを作成する
Form::textarea():テキストエリアを作成する
Form::select():セレクトボックスを作成する
Form::selectRange():選択肢の範囲を指定してセレクトボックスを作成する
Form::selectYear():年を選択するセレクトボックスを作成する
Form::selectMonth():月を選択するセレクトボックスを作成する
Form::checkbox():チェックボックスを作成する
Form::radio():ラジオボタンを作成する
Form::reset():リセットボタンを作成する
Form::image():画像ボタンを作成する
Form::submit():Submitボタンを作成する
Form::button():ボタンを作成する
Form::getSessionStore():フォームで利用するセッションクラスを取得する
Form::token():CSRFトークンを生成する
Form::getIdAttribute():フィールド名のID属性を取得する
Form::getValueAttribute():優先度を付けた入力値を取得する
Form::old():セッションから前の入力値を取得する
Form::oldInputIsEmpty():以前の入力値が空かどうかを確認する
Form::getSelectOption():選択オプションを取得する
Form::setSessionStore():フォームで利用するセッションを設定する

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

Laravel掲示板に画像投稿機能を追加してみた!

paizaラーニングのLaravel入門講座で作成した掲示板に画像投稿機能を付けてみました。

画像投稿の仕組み

1.新規投稿ページからファイル選択して画像を投稿
2.storeメソッドでpublicフォルダに画像を格納。ファイルパスをデータベースに格納
3.ファイルパスを読み取り、画像を表示

1.新規投稿機能を追加

新規投稿ビューにファイルアップロードのフィールドを追加する

new.blade.php
<h1>MYPICTURE</h1>
<p>{{$message}}</p>
{{Form::open(['route'=>'picture.store','files'=>true])}}
<div class="form-group">
    <!--投稿した画像のユーザーネームをDBに格納させる-->
    {{Form::label('user_name','Name:')}}
    {{Form::text('user_name',null)}}
</div>
<div class='form-group'>
    <!--投稿したコメントをDBに格納するビュー-->
    {{Form::label('content','Content:')}}
    {{Form::text('content',null)}}
</div>
<div class='form-group'>
    <!--ファイルを読み込む-->
    {{Form::file('thefile')}}
</div>
<div class="form-group">
    {{Form::submit('作成する',['class'=>'btn btn-primary'])}}
    <a href='{{ route("picture.list")}}'>一覧に戻る</a>
</div>
{{Form::close()}}

Form::openメソッドの「'files'=>true」はファイルのアップロードを行うことを指定します。
「{{Form::file('thefile')}}」で下図のようなファイルアップロードのフォームが出来上がります。
image.png

シンボリックリンク

ビューにファイルアップロードの処理を記述したので次はコントローラ...の前に、シンボリックリンクを張りましょう。
シンボリックリンクとは、特定のファイルやディレクトリを指し示す別のファイルを作成し、それを通じて本体を参照できるようにする仕組みのことで、あるフォルダから別のフォルダに参照できるリンクを作成します。
Laravelにはこの機能が備わっており、ファイルアップロードはこの仕組みを利用します。
ディレクトリをプロジェクトの直下に移動させ、以下のコマンドをターミナルに打ち込みます。

$ php artisan storage:link

すると、publicの直下に以下のようなフォルダが出来上がります。
image.png
これにより、publicからstorageフォルダにアクセスできるようになりました!

2.storeメソッド

アップロードした画像をフォルダに、画像のファイル名をデータベースにそれぞれ格納するメソッドをstoreメソッドに追加します。

PictureController.php(storeメソッド)
    public function store(Request $request)
    {
        $picture=new picture();
        //投稿した画像とコメントをDBに格納させる
        $picture->user_name=$request->user_name;
        $picture->content=$request->content;
        $filename=$request->file('thefile')->store('public');       //storageフォルダに投稿した画像を保存しファイルパスを格納
        $picture->image=str_replace('public/','',$filename);        //ファイル名から「public/」を取り除く
        $picture->save();
         //tinkerコマンドと同じ
        return redirect()->route('picture.show',['id'=>$picture->id]);
    }

アップロードした画像ファイルはstore('')関数でランダムで名前が付けられ、指定したディレクトリに保存されます。(この場合はpublic以下のフォルダに指定)
この時、デフォルトで「storage/app/」に保存され、ファイル名に「public/」と付いてしまう為、ファイル名からこれを取り除く為に一旦$filenameにファイル名を格納し、str_replace関数で「public/」を指定することで取り除いています。
あとは投稿者名やコメントと共にデータベースにファイル名を格納させます。

3.ファイルパスを読み取り、画像を表示

showメソッドとビューを以下のように記述します。

showメソッド

PictureController.php(showメソッド)
    public function show(Request $request,$id,Picture $picture)
    {
        $message='This is your picture.'.$id;
        $picture=Picture::find($id);
        Storage::disk('local')->exists('public/storage/'.$picture->image);
         //$idに格納された番号と一致したデータを引っ張り出す。
        return view('show',['message'=>$message,'picture'=>$picture]);
    }

今回はローカル環境でこのアプリを作ったため、投稿した画像はローカルディスクに保存されます。
なので、「Storage::disk('local')」でローカルディスクを読み取り、「exists」に第一引数として「public/storage/」を、第二引数としてデータベースのimageカラムに格納されているファイル名をそれぞれ指定させます。

ビュー

詳細ページのビューに以下の記述を追加します。

<p><img src="{{ asset('/storage/'.$picture->image)}}"></p>

これでstorageフォルダ直下に格納された画像ファイルを、ファイル名ごとに表示することができました!

完成形

以上のことを踏まえて作成した画像投稿機能ですが、実際はこんな感じになります。

新規投稿画面

image.png

詳細ページ

image.png

課題

今回は「画像を投稿させ、それを表示させる。」ことを最優先にしたので、以下2つの課題があります。

保存するフォルダについて

今回は「storage/app/public」直下に画像を保存させましたが、これだとアプリを公開した際に外から画像のフォルダが丸見えになってしまいます。
ローカル環境ではまだ良いかもしれませんが、公開するとなるとセキュリティ面で非常に危険な状態なので、投稿した画像のフォルダが外から見えないようにしなければなりません。

ファイルのアップロードについて

「画像をアップロードする」って言ってるくせに、実際に組んだのは「ファイルのアップロード」の機能です。
これだと、画像に限らず、テキストだろうが何だろうが「ファイル」という形の物でしたら基本的に何でもアップロードできます。
これを「画像ファイルのみ」にするには、「バリデーション」というファイルの種類を指定する処理が必要になります。

以上2つ、課題として挙げた機能を今後追加していこうと思います。

まとめ

今回はLaravel掲示板に画像投稿機能を追加してみました。
正直、思い付きで「掲示板に画像を投稿出来るようにした方がよくね?」と考えて実装してみましたが、実際にやってみるとかなり難しく、時間がかかってしまいました...。
途中、teratailで質問を投げましたが、回答やアドバイスをして下さった方々に、この場を借りて御礼を申し上げます。

参考文献

[Laravel] ユーザーのアイコン画像を投稿、表示させる機能の実装したのでメモ(画像の保存場所は?シンボリックリンクって?)
[Laravel]保存した画像が404エラーで表示されない
Laravel シンボリックリンクで少しハマった話
IT用語辞典 シンボリックリンク
Laravel Recipes ファイルアップロードのフィールドを作成する
Laravelで画像をアップロードする方法

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

【Laravel+MySQL+Docker+nginx】Laravel開発環境をDockerで構築する手順

はじめに

Laravelの勉強するための開発環境作成手順メモです。
自分用なので雑い。

準備

Dockerの設定だったりしていきます。

ディレクトリ

プロジェクトディレクトリ配下に以下2つのディレクトリを作成。

  • Docker
  • server

docker-compose.yml

プロジェクトディレクトリ配下にdocker-compose.ymlファイルを作成。

docker-compose.yml
version: '3'

services:
  php:
    container_name: php
    build: ./docker/php
    volumes:
      - ./server:/var/www

  nginx:
    image: nginx
    container_name: nginx
    ports:
      - 80:80
    volumes:
      - ./server:/var/www
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - php

  db:
    image: mysql:5.7
    container_name: db
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: test_db
      TZ: 'Asia/Tokyo'
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    volumes:
      - ./docker/db/data:/var/lib/mysql
      - ./docker/db/my.cnf:/etc/mysql/conf.d/my.cnf
      - ./docker/db/sql:/docker-entrypoint-initdb.d
    ports:
      - 4306:3306

Dockerfile

Dockerディレクトリ配下にphpフォルダを作成。
その中にDockerfileを作成。

FROM php:7.3-fpm
COPY php.ini /usr/local/etc/php/

RUN apt-get update \
    && apt-get install -y zlib1g-dev libzip-dev mariadb-client \
    && docker-php-ext-install zip pdo_mysql

#Composer install
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

ENV COMPOSER_ALLOW_SUPERUSER 1

ENV COMPOSER_HOME /composer

ENV PATH $PATH:/composer/vendor/bin

WORKDIR /var/www

RUN composer global require "laravel/installer"

PHPの設定

Dockerfileと同階層でphp.iniファイルを作成。

php.ini
[Date]
date.timezone = "Asia/Tokyo"
[mbstring]
mbstring.internal_encoding = "UTF-8"
mbstring.language = "Japanese"

nginx

Dockerディレクトリにnginxフォルダを作成。
その中にdefault.confファイルを作成。

default.conf
server {
  listen 80;

  root  /var/www/public;
  index index.php;

  location / {
    try_files $uri $uri/ /index.php$is_args$args;
  }

  location ~ \.php$ {
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass   php:9000;
    fastcgi_index  index.php;

    include        fastcgi_params;
    fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param  PATH_INFO $fastcgi_path_info;
  }
}

MySQL

Dockerディレクトリにdbフォルダを作成。
その中にmy.cnfファイルを作成。

my.cnf
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci

[client]
default-character-set=utf8mb4

Laravelプロジェクト

プロジェクトのルートディレクトリに移動。
以下コマンドを実行。

docker-compose up -d
docker-compose exec php bash
laravel new

確認

locallhostで画面が表示されれば成功。
やったね。

DB設定

dockerで開発環境を作った場合、.envファイルとdocker-compose.ymlファイルの情報を合わせとかないとmigrateできなかったりとめんどくさいので今やっとく。

DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=test_db
DB_USERNAME=root
DB_PASSWORD=root

これでDBの登録も問題なし。

まとめ

自分用メモなのでめちゃくちゃざっくりしてますが参考になれば。

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

【Laravel】Laravel最低限の基礎メモ

経緯

ずっとRailsをしてたのですが、ありがたいことにPHPメインの自社開発企業に就職できたので現在PHPを勉強しています。

ただMVCの仕組みだったりあんなにRailsで頑張って覚えたのに、私の記憶が頼りないせいで忘れてしまいそうなので、考えが似た感じのPHPフレームワークLaravelで定着させようと思い勉強し始めました。

まだめっちゃ基礎の段階ですが、とりあえず復習のために色々メモします。

Laravel

PHPのフレームワーク。
多分今PHPのフレームワーク=Laravelみたいになってる。

まだ初歩なので何とも言えませんが、今のところRailsと似てるので割と理解しやすい印象です。

デバックツール

composer require barryvdh/laravel-debugbar

artisan

Railsコマンド的なやつ。
Serverディレクトリで実行します。

php artisan ~~~

Model

Model名は単数形。

作成

php artisan make:model Models/name

マイグレーションファイルとコントローラーも同時に作成する場合

php artisan make:model Models/name -mc

マイグレーションファイルだけ一緒に作りたい場合

php artisan make:model Models/name -m

Migration

テーブル作成の履歴的な。
Migration名は複数形。

作成

php artisan make:migration create_names_table

Databaseフォルダに作成される。
カラム追加は公式参照。
https://readouble.com/laravel/5.5/ja/migrations.html
追加したらMigrateする。

php artisan migrate

カラムを追加したいとき

php artisan make:migration add_カラム名_to_カラムを追加したいテーブル名_table —table=カラムを追加したいテーブル名->after('後ろに挿入したい既存カラム名');

Controller

いろんな処理するところ。

作成

php artisan make:controller コントローラー名

記述

class内にpublicでメソッドを作成。

***Controller.php
return view(viewのフォルダ名.表示させたいファイル名);

View

ファイル名には必ず「***.blade.php」とつける。

Route

記述

web.php
Route::get(表示したいViewのフォルダ名/表示したいファイル名,コントローラー名@メソッド名);

ルート一覧書き出し

php artisan route:list > ファイル名.text

ファサード

get,select,where,groupbyなどSQLに近い構文。

***Controller.php
DB::table(テーブル名)->get();

クエリビルダ

***Controller.php
use Illuminate\Support\Facades\DB; //Controllerに貼り付け

Laravel UI

jsやsassを入れる。
ログイン・新規登録機能などをつける。

【参考】
https://www.techpit.jp/courses/laravel6-aws/lectures/13324326

メッセージの日本語化

下記ファイルをlangフォルダにコピー。
https://github.com/minoryorg/laravel-resources-lang-ja

config/app.phpのlocationがjaになってるか確認。

まとめ

公式見るのがぶっちゃけ一番早い。
Railsと違ってわかりやすかったのでとりあえず困ったら公式でまず検索かけるのが良いと思いました。

あくまでメモなので随時追加していくと思います。

【公式】
https://readouble.com/laravel/7.x/ja/releases.html

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