- 投稿日:2020-12-15T23:59:31+09:00
Laravel de パスワードチェッカー
昨今では辞書型攻撃が......
という前置きはゴミ箱に捨てておいて、漏洩が確認されたパスワードを公開しているHave I Been PwnedのAPIを活用し、脆弱なパスワードで登録できないようなバリデーションを作成していきます。APIのドキュメントを読む
今回は、送信されたパスワードが漏洩履歴があるのか、またどれだけあるのかどうかを知りたいので、Pwned PasswordsのAPIを活用していきます。
詳細は、ドキュメントを読んでいただきたいのですが、要約すると、
- パスワードをSHA1でハッシュ化。
- 先頭5文字を送信(大文字小文字問わず)
- 先頭5文字までで一致したものすべてを、6文字目以降と漏洩回数ともに返却
- なんやかんやしてちょ
です。
実装しようぜ
土台作り
cmd> php artisan make:rule SafePasswordapp\Rules\SafePassword.php<?php namespace App\Rules; use Illuminate\Contracts\Validation\Rule; class SafePassword implements Rule { /** * Create a new rule instance. * * @return void */ public function __construct() { // } /** * Determine if the validation rule passes. * * @param string $attribute * @param mixed $value * @return bool */ public function passes($attribute, $value) { // } /** * Get the validation error message. * * @return string */ public function message() { return 'The validation error message.'; } }これで土台部分はできました。
ゴリゴリ書く
app\Rules\SafePassword.phpのpasses()の中// パスワードをSHA1でハッシュ化 $hash_password = strtoupper(hash('sha1', $value)); // ハッシュ値の先頭5文字を変数定義 $first_section = substr($hash_password, 0, 5); // ハッシュ値の6文字目以降を変数定義 $second_section = substr($hash_password, 5); // cURLリソースの新規作成 $curl_handle = curl_init('https://api.pwnedpasswords.com/range/'.$first_section); // cURLの設定 curl_setopt_array($curl_handle, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 3 ]); // 結果を変数に保存 $response = curl_exec($curl_handle)."\n"; // そもそも結果の中に存在するか検索 if (preg_match("/$second_section:\d*\r?\n/", $response) === 1) { // 存在した場合、「何回」漏洩したか取得 $count = (int) preg_replace("/([\s\S]*)$second_section:(\d*)\r?\n([\s\S]*)/", "$2", $response); // 漏洩回数の許容値をconfigで取得(直接定義しても可) $allow_leak_time = (int) config('auth.allow_password_leak_time'); // もし、許容以上の漏洩回数ならfalse if ($count > $allow_leak_time) return false; // 許容以下ならtrue return true; } // そもそも漏洩してなかったらtrue return true;実際に指定する
適当なController,Request'password' => ['required', 'confirmed', 'min:8', \App\Rules\SafePassword()]コード書いてみて
最初のほうはforeachを使ったりして正規表現を使わないように努力しましたが結局正規表現が楽でした。。
似たような正規表現を二回使っているのはあまり美しくない(と個人的には)思っているのでおいおい修正していきたいと思います。
(強引すぎるという自覚はあるので「こんな方法いいよ!」等ありましたらお教えいただけると幸いです。)作成後記
23:59作成終了!
- 投稿日:2020-12-15T23:56:20+09:00
dockerコンテナでlaravel php artisan serveする時のhost指定
host=0.0.0.0で起動する必要がある。
デフォルトだと127.0.0.1でlistenされるが、これはあくまでコンテナ内でlistenしている。
ホストpcからcurl localhostでアクセスすると、コンテナ内に通信する際に別の内部ipに変換される。
そのため127.0.0.1でlistenしてしまうと応答できない。
そのため0.0.0.0でlistenする。こうするとコンテナ内の全てのインターフェース?でlistenする。
そのため応答できるようになる。↓コードは/appにマウントされる想定
FROM centos:centos8 RUN dnf module install -y php:7.4 WORKDIR /app CMD [ "php", "artisan", "serve", "--port=80", "--host=0.0.0.0" ]
- 投稿日:2020-12-15T20:27:27+09:00
[Laravel]早めのクリスマスviewが真っ白問題
Laravelのviewが真っ白だ。
題名の通り、なぜかLaravelのviewが真っ白になったので、そのことについて残しておきます。ちなみに原因はめちゃめちゃ単純なことでした。
環境
- windows10
- Laravel6
- php(7.4.6)
やろうとしていたこと
TestController.phpclass TestController extends Controller { public function index($id) { $users = Users::find($id); return view('test',['users' => $users]); } public function post() { return view('test.view'); }web.phpRoute::get('/test/{id}', 'testController@index'); Route::get('/test/post', 'testController@post');てきな記述をしていたのですが、真っ白状態です。。。。
ググっていったところ、web.phpの順番が問題そう。web.phpRoute::get('/test/post', 'testController@post'); Route::get('/test/{id}', 'testController@index');変えてみたら、直った!
原因
結果原因はweb.phpの順番というよりは、{id}の部分が邪魔していたそう。
最初の通り、web.phpRoute::get('/test/{id}', 'testController@index'); Route::get('/test/post', 'testController@post');このように記載していると、最初のRouteで/test/{id}を読み込んでしまい、test/postと送っても、ルーティングでpostをidと判断してしまい、そのようなidはないため、真っ白になっていた。
そのため、順番を変えると、先にtest/postが先に読み込まれるので、viewを返したのですね。
このままでも動くのですが、何かの間違いでまたidを読み込まれてもめんどくさいので、このように直しました!web.phpRoute::get('/test/{id}', 'TravelController@showDetail')->where('id', '[0-9]+');こうすると、idは数字しかとってこなくなり、先ほどの順番を変えても、先に読み込まれることはなくなりました。
まとめ
以上がLaravelからのクリスマスプレゼントでした、この先も色々なプレゼント(エラー)をいただくと思いますが、ありがたく受け取ってうまく使いこなしていこうと思います。
参考
- 投稿日:2020-12-15T19:10:50+09:00
LaravelでリクエストのSTART/ENDログを出してみた
会社のTechBlogでCloudWatchにログファイルを表示させたのですが、その際にLaravelのログで開始、終了ログを出してみようとして、処理が長くなりそうだったので別記事にしてみました。
ログの目的としてはこんな感じです。
- 開始と終了のログ2つを出す
- 処理の結果をある程度分かるようにする(success,validate,exceptionとか)
- 処理時間をミリ秒で出す
- apacheのログと合わせて見られるようにIPを出す
- 開始と終了のログを出すので2つを繋げるIDを出す(リクエストID)
- 同じユーザ(同セッション)の操作は追えるようにする(セッションID)
と結構多い。
でもこれぐらいあれば、操作を追える、遅い処理を追えるので実装してみました。
環境
- Laravel v8.18.1 (PHP v7.3.20)
ログを出すミドルウェアを実装する
artisanでmiddlewareを作成し、処理を記述
php artisan make:middleware RequestMiddleware<?php namespace App\Http\Middleware; use Closure; use Exception; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; use Str; class RequestMiddleware { public function handle(Request $request, Closure $next) { //開始終了ログを出したくない場合のコード //AWS ELBのHealthCheckerとか if ($this->isSkipLog($request)) { return $next($request); } $resultType = 'success'; $start = microtime(true); //開始時間 //requestのidとsessionのid。hashしてるのは単にログ表示を短くするため $rid = hash('crc32', Str::uuid()->toString()); $sid = hash('crc32', session()->getId()); $ip = $this->getIp(); //startのログ. RIDとかSIDはhashが2つもあるのでわかりやすくしてる Log::info(implode(' ', [ 'path:', $request->path(), 'START', $request->method(), $ip, 'RID' . $rid, 'SID' . $sid, ])); $result = $next($request); if ($this->isValidate($request)) { //validateメッセージがあれば設定 $resultType = 'validate'; } else if ($this->isException($result)) { //Controllerでexceptionが発生していれば設定 $resultType = 'exception'; } //endログ Log::info(implode(' ', [ 'path:', $request->path(), 'END', $resultType, $ip, explode('.', ((microtime(true) - $start) * 1000))[0], //ミリ秒表示 'RID' . $rid, 'SID' . $sid, ])); return $result; } private function getIp(): string { //ALB経由とかの場合はX_FORWARDED_FORを出す if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { return $_SERVER['HTTP_X_FORWARDED_FOR']; } return $_SERVER['REMOTE_ADDR']; } private function isValidate(Request $request): bool { return !is_null($request->session()->get('errors')); } private function isException($result): bool { return !empty($result->exception); } private function isSkipLog(Request $request): bool { $isSkip = Str::startsWith($request->header('User-Agent'), 'ELB-HealthChecker'); return $isSkip; } }App\Http\Kernel.phpに作ったミドルウェアを設定する
webのミドルウェアグループに追加する
... protected $middlewareGroups = [ 'web' => [ \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, // \Illuminate\Session\Middleware\AuthenticateSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, \App\Http\Middleware\RequestMiddleware::class, //ADD ], ...ログを確認する
適当に作ったHelloControllerで確認
[2020-12-15 09:17:25] local.INFO: path: hello START GET 192.168.33.1 RIDe6fe0217 SID0a1be91a [2020-12-15 09:17:25] local.INFO: path: hello END success 192.168.33.1 46 RIDe6fe0217 SID0a1be91a [2020-12-15 09:17:48] local.INFO: path: hello/edit START POST 192.168.33.1 RIDc24c6515 SID0a1be91a [2020-12-15 09:17:48] local.INFO: path: hello/edit END validate 192.168.33.1 50 RIDc24c6515 SID0a1be91a [2020-12-15 09:18:50] local.INFO: path: hello/edit START POST 192.168.33.1 RID8b780f08 SID0a1be91a [2020-12-15 09:18:50] local.ERROR: test throw {"exception":"[object] (Exception(code: 0): test throw at /var/www/html/laravel8/app/Http/Controllers/HelloController.php:37) [stacktrace] ...略 [2020-12-15 09:18:50] local.INFO: path: hello/edit END exception 192.168.33.1 98 RID8b780f08 SID0a1be91a出てますね!
ただこのmiddleware通る前に失敗すると当然ながらログ出ません。(csrfエラーとか)感想
とりあえずこれで目的のログを出す1例にはなったと思います。
結構手間だったのと、もっと綺麗にコードかけないかなー?ってところは所々あります...まだphper歴低いので指摘あれば是非に。
ちなみに失敗談として、最初間違えてRequestMiddlewareをwebのmiddlewareGroupsではなく共通のmiddlewareに追加してしまい、sessionidが毎回変わってました。
- 投稿日:2020-12-15T18:06:11+09:00
PHP8.0 + Laravel6.x(LTS)環境をDocker Composeで手早く作る
本記事はAll About Group(株式会社オールアバウト) Advent Calendar 2020 15日目になります。
概要三行
- LAMP(PHP8.0) + Laravelの簡易開発環境をDocker Composeで作成
- 基本コピペで誰でも作業できるように記述。
{}で囲んでいる箇所は任意に書き換えてください。概要
PHP8.0が正式リリースされましたね。https://www.php.net/releases/8.0/en.php
実行速度の改善はありがたい限りです。
気になる楽しみ!ということで、今回はPHP8で遊べるように簡易のLAMP+laravel開発環境を構築いたしました。環境構築_概要
使用技術 / 環境
- Docker Compose
- centos7
- PHP8.0
- laravel6.x(LTS)
- MySQL(5.7)
- phpMyAdmin
構成について
先に、今回作成する開発環境の構成を説明しておきます
{開発環境_命名は自由} / ├── docker-compose.yaml ├── web_php8/ | ├── Dockerfile | └── setup/ | ├──.bashrc | └── httpd/ | └── app.conf └── repository/ └── {laravelアプリ(命名は自由)}
名称 種別 概要 {開発環境_命名は自由}/ ? ディレクトリ 今回の開発環境ファイルを束ねるディレクトリ docker-compose.yaml ? ファイル docker composeの実行ファイル web_php8/ ? ディレクトリ WEB系コンテナの関連ファイル置き場です web_php8/Dockerfile ? ファイル 今回作成するWEB系コンテナのDocker Fileです web_php8/setup/ ? ディレクトリ WEB系コンテナ内での設定ファイル郡です web_php8/setup/.bashrc ? ファイル WEB系コンテナ内に適用するbashrcです web_php8/setup/httpd/ ? ディレクトリ WEB系コンテナ内に適用するApacheの設定ファイル郡です web_php8/setup/httpd/app.conf ? ファイル WEB系コンテナで摘要するconfファイルです repository/ ? ディレクトリ WEBサーバーで稼働させるWEBアプリ置き場です repository/{laravelアプリ(命名は自由)} ? ディレクトリ 今回WEB系コンテナ内で稼働させるLaravelアプリです 本件で立ち上がるコンテナは、WEB、DB、PHP-MY-ADMINの3つです。
最低限の環境なので、自由に変更/追加するといいと思います。
repositoryディレクトリに保管したアプリをWEBコンテナ内にマウントしており、
アプリ開発する際には、localのrepository/{laravelアプリ(命名は自由)}を編集すれば、コンテナ内に反映されます。URL
今回の構築にて作成されるURLを記載します
名称 URL アプリURL http://localhost/ phpMyAdmin http://localhost:89/ 前提
- 筆者はMac端末で作業しております。
{}で囲んでいる箇所は任意に書き換えてください。- docker-composeは既に入っているものとします。
環境構築_手順
docker-compose.yamlの作成
複数のdockerコンテナを束ねるDocker Composeのyamlファイルを作成します。
※ {}で囲んでいる箇所は任意に書き換えてくださいmkdir {開発環境_命名は自由}; cd {開発環境_命名は自由} vim docker-compose.yaml下記を
docker-compose.yamlに記述してくださいdocker-compose.yamlversion: '3' services: web_php8: build: context: ./web_php8 volumes: - ./repository/{laravelアプリ(命名は自由)}:/var/www/{laravelアプリ(命名は自由)} - ./web_php8/setup/.bashrc:/root/.bashrc ports: - "80:80" working_dir: /var/www/{laravelアプリ(命名は自由)}/ environment: TZ: "Asia/Tokyo" db: image: mysql:5.7 ports: - "3306:3306" environment: MYSQL_DATABASE: test_db MYSQL_USER: test_user MYSQL_PASSWORD: test_pass MYSQL_ALLOW_EMPTY_PASSWORD: "yes" TZ: "Asia/Tokyo" php-my-admin: image: phpmyadmin/phpmyadmin links: - db ports: - "89:80" environment: MYSQL_USERNAME: test_user MYSQL_ROOT_PASSWORD: test_pass PMA_HOST: db PMA_PORT: 3306立ち上げる各コンテナとその設定を定義しています。
WEBコンテナはPHP8.0の導入など、色々やりたいので、個別でDockerFileを作成します。WEB系コンテナの作成①Docker File編
WEB系のDockerコンテナを立ち上げるためのDocker Fileを作成します。
mkdir web_php8; cd web_php8 vim Dockerfile下記を
Dockerfileに記述してくださいFROM centos:centos7 # timezone RUN rm -f /etc/localtime \ && cp -p /usr/share/zoneinfo/Japan /etc/localtime # php8.0 RUN yum -y update \ && yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm \ && yum -y install https://rpms.remirepo.net/enterprise/remi-release-7.rpm \ && yum -y install yum-utils \ && yum-config-manager --disable 'remi-php*' && yum-config-manager --enable remi-php80 \ && yum remove php* \ && yum -y install php php-{cli,fpm,mysqlnd,zip,devel,gd,mbstring,curl,xml,pear,bcmath,json} \ && php --version # apache RUN yum install -y httpd httpd-devel \ && rm -rf /var/cache/yum/* \ && yum clean all \ && mkdir -p /app/ENV /AccessLog.local COPY ./setup/httpd/app.conf /etc/httpd/conf.d/app.conf # composer RUN cd /tmp && curl -sS https://getcomposer.org/installer | php \ && mv ./composer.phar /usr/bin/composer CMD ["httpd", "-D", "FOREGROUND"]WEB系コンテナに必要なツール類や設定はここで定義しています。
WEB系コンテナの作成②関連ファイル編
WEB系コンテナ内にて摘要する各ファイルを作成しておきます
まずは、コンテナ内に適用するbashrcを作成します
mkdir setup; cd setup vim .bashrc中身は自由ですので、皆さんのお好きな設定を記述ください。
(マウントしちゃってもいいと思います)次にApache confファイルを作成しておきます
mkdir httpd; cd httpd vim app.conf
app.confに下記を記述してくださいapp.conf<VirtualHost *:80> ServerName localhost DocumentRoot /var/www/{laravelアプリ(命名は自由)}/public ErrorLog "/dev/stdout" CustomLog "/dev/stdout" combined <Directory /var/www/{laravelアプリ(命名は自由)}/public> AllowOverride All Options Includes FollowSymLinks Order allow,deny Allow from all </Directory> </VirtualHost>Laravelアプリケーション_①アプリのclone
実際に稼働するLaravelアプリを作っていきましょう。
ディレクトリを移動して、laravelをgit cloneします。
ここではWEBアプリはrepositoryディレクトリ配下で管理するようにしています。cd ../../../ mkdir repository; cd repository git clone -b 6.x https://github.com/laravel/laravel.git {適当なアプリ名}環境の立ち上げ
下記にてdocker-composeを実行。
cd で移動しまくってて、わかりにくく申し訳ないですが
docker-compose.yamlと同階層で実行すればOKです。cd ../ docker-compose up各環境が立ち上がり、各コンテナのログが流れ始めたことを確認します。
Laravelアプリケーション_②初期設定
laravelをcloneした後に実行するお作法です。
権限とcomposer、そしてenv周りcd repository/{laravelアプリ(命名は自由)} chmod -R 777 storage chmod -R 777 bootstrap/cache docker-compose exec web_php8 /bin/bash -c “composer install” cp -p .env.example .env php artisan key:generateこの時点で http://localhost:/ にアクセスし、Laravelのテストページが表示されていたら、構築成功です。
Laravelアプリケーション_③DB周り初期設定
.envに本環境のDB設定を記述しましょう
sed -e 's/DB_HOST=127.0.0.1/DB_HOST=db/' .env sed -e 's/DB_DATABASE=laravel/DB_DATABASE=test_db/' .env sed -e 's/DB_USERNAME=root/DB_USERNAME=test_user/' .env sed -e 's/DB_PASSWORD=/DB_PASSWORD=test_pass/' .env接続テスト兼、DBマイグレーション
下記にてmigrateを実行をし、デフォルトのテーブル郡が作成できれば成功です。docker-compose exec web_php8 /bin/bash -c “cd /app/operation-cms && php artisan migrate方法は何でもいいですが一例として。
下記にてphpMyAdminからDB内を確認できます。
http://localhost:89/
- 投稿日:2020-12-15T17:57:45+09:00
summernote on laravel の導入備忘録
インストール(npm)
npm install summernoteでnode_modulesに入る
レイアウトがいろいろ用意されている。
通常はrequire('summernote');で行けるが
日本語化とレイアウトをbootstrap4ベースにしたかったけどサンプルなかった。
多分以下で行けるはず。resources/js/bootstrap.jsrequire('summernote/dist/summernote-bs4.min'); require('summernote/dist/lang/summernote-ja-JP.min');resources/js/app.scss@import 'summernote/dist/summernote-bs4.min';フロント側(bladeファイルとjavascript)
divタグを用意するだけ。textareaタグでもよい。
edit.blade.php<div id="summernote"></div> <textarea id="summernote" class="form-control" name="description"></textarea>edit.js$(document).ready(function() { $('#summernote').summernote({ lang: 'ja-JP', height: 300, } }画像挿入時のサーバ転送
デフォルトはimgタグにbase64エンコードされたデータとして挿入されます。
独自のサーバにアップロードしてそのURLをimgタグに挿入する方法も用意されている。$('#summernote').summernote({ ...略... callbacks: { onImageUpload: (files) => { /* ローカルファイルを選んだあと呼ばれるのでサーバーへアップする */ insertImageFile(files[0]); }, onMediaDelete: (target) => { /* 画像を削除するを選んだ時呼ばれるのでサーバーへ削除要求する */ removeImageFile(target[0].src); }, }, ...略... function insertImageFile(file) { let formData = new FormData(); formData.append('file', file); fetch('/insert-image-file', formData,...) .then((response) => { if (response.ok) { // サーバ側で応答に画像のurlを付与する let imgNode = document.createElement('img'); imgNode.src = response.url; $('#summernote').summernote('insertNode', imgNode); } }); } function removeImageFile(src) { /* 削除のアクションを呼ぶ */ let formData = new FormData(); formData.append('src', src); fetch('/remove-image-file', formdata, ...) .then((response) => { if (response.ok) { } } }サーバ側(laravel/php)ではinsert-image-fileとremove-image-fileに対応する
アクションを用意する。app/Http/Controller/TestController.phppublic function insertImageFile(Request $request) { /* ファイル名をuuidに変換 */ $uuid = (string) Str::uuid(); $ext = $request->file('file')->extension(); $file = $uuid . '.' . $ext; $imgpath = $request->file('file')->storeAs('attachments', $file, 'public'); $url = Storage::disk('public')->url($imgpath); return response()->json(['result' => 'OK', 'url' => $url]); } public function removeImageFile(Request $request) { /* storageまでのパスを取得 */ $url = Storage::disk('public')->url(''); /* 画像のパス(URL)からサーバ内のローカルパスに変換 */ $delete_file = str_replace($url, '', $request->src); $resp = Storage::disk('public')->delete($delete_file); return response()->json(['result' => 'OK']); }フォームのsubmitアクション
HTMLエディタの入力欄に対応するinputタグがないので
submitのクリックイベントを引っ掛けて隠し属性のinputを追加してsumbmitする
buttonタグにname="submit"があるとformのsubmitが効かないので削除する。
追記(2020/12/15)
普通にtextareaタグにid設定すればsummernoteのレイアウトになるっぽいので
以下の処理は必要ない。$("#submit_button").click((e) => { /* 一旦submitを無効 */ e.preventDefault(); let description = $('#summernote').summernote('code'); let edit_form = document.getElementById('edit_form'); if (edit_form != null) { inputDescription = document.createElement("input"); inputDescription.type = 'hidden'; inputDescription.name = 'description'; inputDescription.value = description; edit_form.appendChild(inputDescription); edit_form.submit(); } });
- 投稿日:2020-12-15T17:10:49+09:00
Method Illuminate\Http\UploadedFile::getClientSize does not exist. と出た場合の対処法
- 投稿日:2020-12-15T16:09:08+09:00
[PHP]複数配列のループ
複数配列を同時にループさせる方法として以下のようにarray_map()を使うのが便利だったので共有します。
.php$array1 = [1,2,3]; $array2 = [4,5,6]; foreach (array_map(null, $array1, $array2) as [$arr1, $arr2]) { echo ($arr1 + $arr2); //5,7,9 }array_map(null, $array1, $array2)の結果は [ [1,4], [2,5], [3,6] ] となっていて、
nullを渡すことによって、それぞれの配列の同じインデックスの要素同士をくっつけて、配列の配列にしてくれます。会社の紹介
私は現在、株式会社ダイアログという物流×ITの会社に勤務しております。
2020年12月現在、エンジニアの募集はしていませんが、他にも様々な職種を募集しているので、Wantedlyのページをご覧ください。
- 投稿日:2020-12-15T15:51:33+09:00
LaravelにおけるORMについて
ORMとは?
アプリケーションからデータを操作しやすくするための装置。
アプリ側(オブジェクト指向)はクラス内のプロパティにデータを持たせる。
データベース(リレーショナルデータベース)はテーブルにデータを持たせる。アプリ側
class User { private $email; private $password; // 中略 }データベース側
users テーブル | email | password | |-----------------|----------| | sample@mail.com | test1234 |つまり構造上繋がりをもっておかなければならない「モデル」と「データベース」のデータの持ち方がそれぞれバラバラということ。
これによって何が起こるかというと、例えばテーブルから取り出したデータ形式をいちいち変えて改めてモデルに格納しなければならなかったり、逆にモデルのデータの変更した場合、それとデータベースを対応させるために改めてインサート文を書くことになる。つまり、「片方が変更した場合それと対応させるためにもう片方もその形式に準じて改めて変更させなければならない」という手間が生まれてくる。
そこで「アプリ側のデータとデータベース側のデータの紐付けをあらかじめ定義しておこう!」というのがORM。
アプリとDBのデータの紐付け
ではどうやって紐付けるか?
まずモデルを作成する。モデルクラスはPHP側のデータの入れ物になるクラス。
基本的にはモデルひとつにテーブルひとつが対応するように作成する。$ php artisan make:model GroupするとappフォルダにGroupクラスが作成される。
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Folder extends Model { // }クラスに何も書かれていないが、継承元のModelクラスに様々な設定が書き込まれているためこれだけでモデルとして機能する。
このModelクラスが「アプリ側のデータとデータベース側のデータの紐付け」を実現してくれる。
どういうことかというと、このクラス名の複数形の名前のテーブルと自動的に紐づくように設定してくれる。ここでいうと、クラス名を「Group」にしたので「Group」クラスは「groups」テーブルと自動的に紐づくことになる。こうしてModelクラスの紐付け(ORM)によって、モデルクラスとデータベースでデータの持ち方に違いがあったが、開発者は今まで通りオブジェクト指向に沿った書き方でデータベース側とデータのやり取りを行うことができる。
- 投稿日:2020-12-15T15:49:41+09:00
MVCモデルのメリット
- 投稿日:2020-12-15T14:59:53+09:00
docker環境でlaravel/Duskをインストール(laravel6)
煮詰まっちゃったんで記事に残しておきます
前提条件
simotarooさんの絶対に失敗しないDockerでLaravel+Vueの実行環境(LEMP環境)を構築する方法、もしくはdockerにてlaravel,PHPの環境構築済みの方
環境
mac
docker (LEMP環境)
構築方法
任意のdockerfileに以下を追加しbuildし直す
※僕はsimotarooさんの記事を参考にして構築したためphpのdockerfileに記述しましたdockerfileRUN apt-get -y install libzip-dev RUN docker-php-ext-install zip RUN apt-get -y install libnss3 RUN apt-get -y install libasound2-data libasound2 xdg-utils RUN apt install sudo RUN apt-get install -y wget RUN apt-get install -y gnupg2 RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - RUN echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list RUN sudo apt update RUN sudo apt-get install -y google-chrome-stable RUN apt-get install -y fonts-ipafont.envのAPP_URLを書き換え
.envAPP_URL = http://localhost を APP_URL=http://127.0.0.1:8000 に書き換えtests/DuskTestCase.phpを書き換え
tests/DuskTestCase.phpprotected function driver() { $options = (new ChromeOptions)->addArguments([ '--no-sandbox', '--disable-gpu', '--headless', '--window-size=1920,1080', '--lang=ja_JP', ]);インストール後プロジェクト内で
composer require --dev laravel/dusk: artisan dusk:install php artisan serveそんでテスト実行
php artisan dusk PHPUnit 8.5.8 by Sebastian Bergmann and contributors. . 1 / 1 (100%) Time: 11.2 seconds, Memory: 24.00 MB OK (1 test, 1 assertion)成功したら無事完成です
- 投稿日:2020-12-15T13:20:56+09:00
Laravelで簡易的にBasic認証
LaravelでBasic認証をやる方法はいくつかありますが、ライブラリを入れたりせず、データベース使ったりせず、簡易的にに実現する方法です。
- ミドルウェアを作成する(1ファイル)
- ミドルウェアをKernelに記述する
- routes/web.php で認証の対象のページを記述する
の3ステップで実現できます。
ミドルウェアを作成する
今回のサンプルでは、BasicAuthMiddlewareという名前でミドルウェアを作成します。
php artisan make:middleware BasicAuthMiddleware作成したミドルウェアのhandleメソッドを、以下のように実装します。
ユーザー名とパスワードは、ここに直接書いてます。App\Http\Middleware\BasicAuthMiddleware.phppublic function handle(Request $request, Closure $next) { $username = $request->getUser(); $password = $request->getPassword(); if ($username == 'sample' && $password = 'sample') { return $next($request); } abort(401, "Enter username and password.", [ header('WWW-Authenticate: Basic realm="Sample Private Page"'), header('Content-Type: text/plain; charset=utf-8') ]); }ミドルウェアをKernelに登録
app/Http/Kernel.php の $routeMiddleware に、作成したミドルウェアを記述。
今回は、'basicauth'という名前で登録app/Http/Kernel.php'basicauth' => \App\Http\Middleware\BasicAuthMiddleware::class,バージョン5くらいのLaravelだと、下記の書き方になります。
'basicauth' => 'App\Http\Middleware\BasicAuthMiddleware',routes/web.php で認証の対象のページを記述する
登録した名前のミドルウェアで、対象のページをグループ化する
routes/web.phpRoute::group(['middleware' => 'basicauth'], function() { // ここに対象のページを記述 // 例) Route::get('/', [App\Http\Controllers\TopController::class, 'index'])->name('top'); });以上で完了です。
- 投稿日:2020-12-15T10:32:41+09:00
Dockerを使ってLaravelのローカル開発環境を作る(Apache版)
Laravel Advent Calendar 2020 - Qiita の 15日目 の記事です。
昨日は @noel_kuma さんのLinter / FormatterからLaravelへの贈り物の記事でした!
明日は @kyoya0819 さんの記事です!概要
最強のLaravel開発環境をDockerを使って構築する【新編集版】
この記事の反響が多く、Apache版も作って欲しいという要望もあってので記事を書いてみました。
対象読者
- Laravelを愛する心の持っている方
- PHP, Linux, Git, Dockerの知識をある程度持っている方
リポジトリ
https://github.com/ucan-lab/docker-laravel-apache
使い方
A. Laravelプロジェクトの新規作成
$ git clone git@github.com:ucan-lab/docker-laravel-apache.git $ cd docker-laravel-apache $ make create-project以上の3ステップでLaravelの新規プロジェクトの環境構築は完了です。
A'. Laravelプロジェクトをバージョン指定して新規作成
MakefileのLaravelインストール部分を書き換えます。docker-compose exec app composer create-project --prefer-dist "laravel/laravel=6.*" .B. 既存のLaravelプロジェクトの環境を構築する
$ git clone git@github.com:ucan-lab/docker-laravel-apache.git # Laravelプロジェクトを docker-laravel-apache/backend へクローンする $ git clone git@github.com:laravel/laravel.git docker-laravel-apache/backend $ cd docker-laravel-apache $ make initこれで既存のLaravelプロジェクトの環境構築は完了です。
コンテナ構成
├── web └── dbweb, db の2つのコンテナ構成で進めます。
nginxとの違い
Nginxの場合は、php-fpm(アプリケーションサーバー)とコンテナを分けていました。
Apacheの場合は、mod_phpというモジュールがデフォルトでインストールされており、それがPHPを実行してくれるアプリケーションサーバーを兼ねるウェブサーバーです。ディレクトリ構成
. ├── backend # Laravelプロジェクトのルートディレクトリ ├── infra │ └── docker │ ├── apache │ │ └── httpd.conf │ ├── mysql │ │ ├── Dockerfile │ │ └── my.cnf │ └── php │ ├── Dockerfile │ └── php.ini ├── .env.example ├── Makefile └── docker-compose.ymlnginx版との差分
※nginx版との違いはnginxのディレクトリがapacheディレクトリに変わったくらいですね?
- infra/docker/apache/httpd.conf
- infra/docker/php/Dockerfile
- Makefile
- docker-compose.yml
変更点に関しては、上記の4つのファイルに注目していただければ良いかなと思います。
プルリクエストで詳細な差分を確認できます。
- https://github.com/ucan-lab/docker-laravel-apache/pull/2
- https://github.com/ucan-lab/docker-laravel-apache/pull/4
PHP のベースコンテナ
イメージタグに
php:<version>-apacheが用意されています。解説など
docker-compose.yml
version: "3.8" volumes: db-store: services: web: build: context: . dockerfile: ./infra/docker/php/Dockerfile ports: - ${WEB_PORT:-80}:80 volumes: - ./backend:/work/backend environment: - DB_CONNECTION=mysql - DB_HOST=db - DB_PORT=3306 - DB_DATABASE=${DB_NAME:-laravel_local} - DB_USERNAME=${DB_USER:-phper} - DB_PASSWORD=${DB_PASS:-secret} db: build: context: . dockerfile: ./infra/docker/mysql/Dockerfile ports: - ${DB_PORT:-3306}:3306 volumes: - db-store:/var/lib/mysql environment: - MYSQL_DATABASE=${DB_NAME:-laravel_local} - MYSQL_USER=${DB_USER:-phper} - MYSQL_PASSWORD=${DB_PASS:-secret} - MYSQL_ROOT_PASSWORD=${DB_PASS:-secret}appとwebコンテナをまとめて、webコンテナのみになりました。
どっちにするか悩みましたけど、webコンテナにしました。解説することないので、次へ行きます。
infra/docker/php/Dockerfile
infra/docker/php/DockerfileFROM node:14-buster as node FROM php:7.4-apache-buster LABEL maintainer="ucan-lab <yes@u-can.pro>" SHELL ["/bin/bash", "-oeux", "pipefail", "-c"] # timezone environment ENV TZ=UTC \ # locale LANG=en_US.UTF-8 \ LANGUAGE=en_US:en \ LC_ALL=en_US.UTF-8 \ # composer environment COMPOSER_ALLOW_SUPERUSER=1 \ COMPOSER_HOME=/composer # composer command COPY --from=composer:2.0 /usr/bin/composer /usr/bin/composer # node command COPY --from=node /usr/local/bin /usr/local/bin # npm command COPY --from=node /usr/local/lib /usr/local/lib # yarn command COPY --from=node /opt /opt RUN apt-get update && \ apt-get -y install git libicu-dev libonig-dev libzip-dev unzip locales && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* && \ locale-gen en_US.UTF-8 && \ localedef -f UTF-8 -i en_US en_US.UTF-8 && \ a2enmod rewrite && \ docker-php-ext-install intl pdo_mysql zip bcmath && \ composer config -g process-timeout 3600 && \ composer config -g repos.packagist composer https://packagist.org COPY ./infra/docker/php/php.ini /usr/local/etc/php/php.ini COPY ./infra/docker/apache/httpd.conf /etc/apache2/sites-available/000-default.conf WORKDIR /work/backendphp, composer, nodeの3つのコンテナをマルチステージビルドして使ってます。
(phpのイメージ内にapacheも入ってます。)nodeいらないよって場合はDockerfileから削除しちゃってください。
英語圏の方にも使ってもらえるように言語&タイムゾーンのデフォルトは英語にしてます。
実際に使うときは日本に変更してご利用ください。
a2enmod rewriteはApacheのmod_rewriteモジュールを有効化してます。
これがないとLaravelのルーティングがうまく機能しなくなります。infra/docker/apache/httpd.conf
infra/docker/apache/httpd.conf<VirtualHost *:80> ServerName example.com ServerAdmin webmaster@localhost DocumentRoot /work/backend/public ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined <Directory /work/backend/public> Options Indexes FollowSymLinks AllowOverride All Require all granted </Directory> </VirtualHost>最低限設定必要な項目だけ設定してます。
- 投稿日:2020-12-15T09:49:08+09:00
閏年じゃない年も2/29生まれの人をお祝いしたい??
これはSOUSEI Technology アドベントカレンダー2020 15日目の記事です。
2/28と3/1どちらで祝うべきなのか
2/29生まれの人、閏年なら当日お祝いしてあげればいいのですが、閏年じゃないときはどうすればいいの?
ググってみました。例年については、男女共に「2月28に祝う」という声が圧倒的。
https://cocoloni.jp/love/232234/あとで祝われると「忘れてたでしょ?」ってなりそう
https://teratail.com/questions/145109ということで2/28にお祝いします!??
あとはデータを取得するだけ!今日が誕生日の人を取得する
当日が2/28かつ閏年でないときに、追加で2/29のデータを取得してあげればOK!
$today = Carbon::now()->toDateString(); $users = User::whereDay('birthday', date('d', strtotime($today))) ->whereMonth('birthday', date('m', strtotime($today))) ->get(); if(date('m-d', strtotime($today)) == '02-28' && !date('L', strtotime($today))) { $users->merge( User::whereDay('birthday', 29) ->whereMonth('birthday', 2) ->get(); ); } return $users;範囲指定(from〜to)で誕生日の人を取得する
これは指定された日付が閏年かどうか気にする必要はなく、toが2/28のときだけ考慮すればOK!
/** * 月日が日付範囲に含まれるスコープ * * @param Builder $query クエリ * @param string $column 対象カラム * @param Carbon $from 開始日 * @param Carbon $to 終了日 * @return Builder */ private function scopeWhereBetweenMonthAndDays($query, $column, $from, $to) { // fromとtoの差が1年以上あるときはすべての日が対象になるのでクエリ追加不要(全データ取得する) if (!$to->diffInYears($from)) { $from_without_year = $from->format('m-d'); // 閏年対応。2/28が指定された場合は2/29として、2/29のデータも取得対象とする $to_without_year = $to->format('m-d') == '02-28' ? '02-29' : $to->format('m-d'); if ($from->year == $to->year) { $query = $query->whereRaw('DATE_FORMAT(' . $column . ',"%m-%d") >= "' . $from_without_year . '"') ->whereRaw('DATE_FORMAT(' . $column . ',"%m-%d") <= "' . $to_without_year . '"'); } else { $query = $query->where(function ($q1) use ($from_without_year, $to_without_year) { $q1->orWhereRaw('DATE_FORMAT(' . $column . ',"%m-%d") >= "' . $from_without_year . '"') ->orWhereRaw('DATE_FORMAT(' . $column . ',"%m-%d") <= "' . $to_without_year . '"'); }); } } return $query; }さいごに
僕の誕生日は3/3です!
プレゼント待ってます!
- 投稿日:2020-12-15T09:43:04+09:00
Laravel Excel から出力した csv のレコードが重複してしまっていた問題を解決した話
はじめに
あるプロジェクトで、
Laravelを使って構築したシステムの DB のデータを csv で出力する要件があり、Laravel Excelを導入して実現していました。しかしあるとき、出力した csv のレコードが重複しているという報告があり、実際に全く同じレコードが2件重複して出力されていることがわかりました。
さらには、出力されるはずのデータが含まれていないことも判明しました。この記事では、この問題を解決するまでの過程を紹介し、自らへの戒めと教訓としたいと思います。
Laravel Excel とは
Laravelで Excel ファイルの操作を行うためのライブラリです。
PhpSpreadsheetというライブラリが元になっていて、Laravelでの操作に特化しています。
今回のプロジェクトでは、このLaravel Excelを使って、主に csv ファイルの入出力を行っています。環境
PHP : 7.3.5Laravel : 5.8PostgreSQL : 11.3Laravel Excel : 3.1.9発生した問題
- 約4万件のデータを抽出した際に、20〜30件ほど重複している
- CSVを出力するたびに、重複するデータが異なる
- データが重複した分だけ、本来であれば存在するデータがなくなっている
- データの総件数は毎回変わらない
犯人探し
PostgreSQL
LEFT JOINのやり方が間違っている?とある関係で
Eager LoadingではなくLEFT JOINを使用しているので、これが真っ先に原因かと思ったのですが、単純なクエリだったのでこれは問題なさそうでした。
SELECT文の書き方が間違っている?
SELECTでテーブル名を指定しておらず、意図しないテーブルのidなどのデータが出力されてしまっているのかとも思いましたが、テーブル名を指定する書き方をしているのでこれも大丈夫そうでした。PostgreSQL の バグ?
こんなバグがあったらもっと叩かれてそう。。
Laravel Excel
Github の issue で同じような問題を探してみる
次にライブラリである
Laravel Excelのバグかと思い、 Github で同じような報告がないか探してみました。
が、これは特に見つかりませんでした。ライブラリの中身をみてみる
中でどのような処理が行われているのかわからなかったので、中のコードを読んでみました。
今回はLaravelのQueryBuilderから csv を作成していたので、途中でchunkメソッド(リファレンス) が使用されていました。
これはDBから取得するデータを小分けにすることで、メモリ使用量を抑え、大量のデータでも出力できるようにしているのだと思います。再度考察
ここまでで具体的な原因はわからなかったので、再度原因を考察してみることにしました(実際は結構頭抱えてました)。
改めて重複しているデータを眺めてみる
よくよく見てみると、何故か重複データは2000行目や5000行目など、1000行目単位の付近で発生していました。
この 1000 という数字が何かのヒントだと思い、調べてみると、Laravel Excelの設定ファイルにchunk_sizeという項目があり、これが 1000 になっていました。
これを試しに 500 にしてみると、重複が1500行目や4500行目など、500の区切りでも発生するようになりました。
chunkによって発行されているクエリを調べてみるSELECT * FROM 'テーブル名' LIMIT 1000 OFFSET 0;そうか、わかったぞ!(cv:高山みなみ)
原因は...
ORDER BYに、ユニークになるカラムを指定していないこと でした。
今回のクエリでは、ユニークでないカラムをORDER BYに指定していたため、このカラムが同じ値の場合、並び順がDBによって決定されてしまっていました。
これとLIMITおよびOFFSETが合わさることにより、クエリを発行するたびに並び順が変更され、境目付近のデータが重複してしまっていたのだと考えられます。この現象については、下記のブログに詳細が記載されていました。
https://blog.mmmcorp.co.jp/blog/2018/03/22/sql_order_by/同じように
LIMIT、OFFSETを使用しているペジネーションなんかでも注意が必要ですね。そして
ORDER BYにユニークとなるカラムを設定したところ、見事に重複がなくなりました。
これで解決ですね。さいごに
というわけで結局犯人は自分だったわけですが。。
原因がわかるとすっきりしますが、わからないうちは辛く苦しかったです。。笑
ライブラリを使っているときに予期せぬことが起きた場合には、ライブラリの中身を見ることが大事ですね。では最後にまとめ
並び順の権を DB に握らせるな!!
- 投稿日:2020-12-15T02:27:35+09:00
【スクショで解説】PHP/Laravelを使ってGoogleドライブにファイルをアップロードしてみる
対象読者
- PHP/Laravelを使ってGoogleドライブにファイルをアップロードしたい人。
コードの実装以外の部分(認証のための準備)でてこずる部分が結構あったため、メモとして保存。
サンプルコードも用意しているので、手順通りに進めれば必ず同じ結果を得られるようになっています。下準備
コードを実装する前に、Googleドライブを外部から利用するために事前にやっておかなければならない事があるのでそちらから手をつけていきます。(APIの有効化や認証アカウントの作成など)
Google Cloud Platformの設定
https://console.cloud.google.com/
まず最初にGoogle Cloud Platform(GCP)の設定を行います。
これまでGCPを利用した事の無い人はスタートガイドに従って利用を開始してください。(クレジットカードなどの情報を入力する必要がありますが、無料トライアル期間があるのでこの時点で勝手に課金される事はありません。)
参照記事: これから始めるGCP(GCE) 安全に無料枠を使い倒せ
任意のプロジェクトを選択
今回はアカウント作成時にデフォルトで用意された「My First Project」を使用します。
Google Drive APIの有効化
左サイドバーから「APIとサービス→ライブラリ」を選択。
各種Google APIが出てくるので、その中から「Google Drive API」を見つけ、有効化します。サービスアカウントを作成
有効化に成功するとGoogle Drive APIの管理画面に飛ぶので、左サイドバーから「認証情報」を選択。
外部からGoogle Drive APIを使用する際、認証に必要な「サービスアカウント」の作成を行います。参照記事: GCP Service Accountを理解する
アカウント名や説明文を入力し、「作成」ボタンをクリック。
その後、アカウントにロール(役割)を付与していきます。(今回はテストなのでProject→オーナーを選択)③は無視でOKなので、そのまま「完了」ボタンをクリックしてください。
上手くいくとこんな感じでサービスアカウントが追加されています。
秘密鍵(JSON)を作成
アカウント詳細画面を開くと、下の方に「鍵を追加」というメニューがあるので、そちらから秘密鍵(JSON)を作成します。
後にこの秘密鍵を使ってアプリからサービスアカウントへの認証を行うので、ダウンロードして保管しておきましょう。
Googleドライブの設定
https://drive.google.com/drive/u/0/my-drive
次にGoogleドライブの設定を行います。
フォルダを作成
テスト用に適当なフォルダを用意しましょう。
サービスアカウントを共同編集者に追加
先ほど作成したサービスアカウントがGoogleドライブ内のフォルダにアクセスできるよう、「共有」を選択してサービスアカウントのメールアドレスを追加します。
(メールアドレスはサービスアカウントの詳細画面から確認可能)
共有が終わったら下準備は完了です。
コードを実装
さて、いよいよコードの実装を行っていきます。
Laravelプロジェクトを作成
誰でも同じ結果になるように、今回はLaravelプロジェクトを作成するところから始めます。
https://github.com/kazama1209/laravel-new
↑サンプルを用意したので、各自クローンして使ってください。
セットアップ
Dockerでちゃちゃっと準備を済ませます。
コンテナを起動
$ docker-compose up -dコンテナ内に入る
$ docker-compose exec php bashプロジェクトを作成
$ composer create-project laravel/laravelブラウザを確認
「localhost」へアクセスしてLaravelの初期画面が表示されていれば成功。
Composerをインストール
https://github.com/googleapis/google-api-php-client
Google Drive APIを使用するために公式のライブラリを準備します。
$ cd laravel $ composer require google/apiclient:^2.0 ... - Downloading phpseclib/phpseclib (2.0.29) - Downloading psr/cache (1.0.1) - Downloading firebase/php-jwt (v5.2.0) - Downloading google/auth (v1.14.3) - Downloading google/apiclient-services (v0.156) - Downloading google/apiclient (v2.8.3) - Installing phpseclib/phpseclib (2.0.29): Extracting archive - Installing psr/cache (1.0.1): Extracting archive - Installing firebase/php-jwt (v5.2.0): Extracting archive - Installing google/auth (v1.14.3): Extracting archive - Installing google/apiclient-services (v0.156): Extracting archive - Installing google/apiclient (v2.8.3): Extracting archive 4 package suggestions were added by new dependencies, use `composer suggest` to see details. Package phpunit/php-token-stream is abandoned, you should avoid using it. No replacement was suggested. Generating optimized autoload files > Illuminate\Foundation\ComposerScripts::postAutoloadDump > @php artisan package:discover --ansi Discovered Package: facade/ignition Discovered Package: fideloper/proxy Discovered Package: fruitcake/laravel-cors Discovered Package: laravel/tinker Discovered Package: nesbot/carbon Discovered Package: nunomaduro/collision Package manifest generated successfully. 68 packages you are using are looking for funding. Use the `composer fund` command to find out more!先ほど作成したプロジェクト内に移動し、Composerをインストール。
Googleドライブにファイルをアップロードするための関数を作成
$ mkdir app/Libs $ touch GoogleDrive.php今回はapp/配下に「Libs」というフォルダを用意し、「GoogleDrive.php」ファイル内に関数を作成していきます。
app/Libs/GoogleDrive.php<?php namespace App\Libs; class GoogleDrive { /** * Googleドライブへの認証を行う * @return Google_Service_Drive */ public function getDriveClient(): \Google_Service_Drive { $client = new \Google_Client(); // サービスアカウント作成時にダウンロードしたJSONファイルの名前を「client_secret」変更し、configフォルダ内に設置 $client->setAuthConfig(config_path('client_secret.json')); $client->setScopes(['https://www.googleapis.com/auth/drive']); return new \Google_Service_Drive($client); } /** * ファイルをアップロードする * * @return GoogleDrive */ public function fileUpload() { $driveClient = $this->getDriveClient(); $fileMetadata = new \Google_Service_Drive_DriveFile([ 'name' => 'sample.jpg', // Googleドライブへアップロードされた際のファイル名(今回は「sample.jpg」とする) 'parents' => ['xxxxxxxxxxxxxxxxxx'], // 保存先のフォルダID(配列で渡さなければならないので注意) ]); $driveClient->files->create($fileMetadata, [ 'data' => file_get_contents(storage_path('app/public/sample.jpg')), // アップロード対象となるファイルのパス(今回はstorage/app/public配下の「sample.jpg」を指定) 'mimeType' => ' image/jpeg', 'uploadType' => 'media', 'fields' => 'id', ]); } }全体的にかなりシンプルな記述にしてあるので、細かい説明は割愛しても大丈夫だと思います。
※アップロードに使用する画像は各自用意してapp/Storage/app/public配下に設置してください。
Artisanコマンドを作成
動作確認しやすいようにArtisanコマンドを作成していきます。
$ php artisan make:command GoogleDriveFileUploadターミナルで↑を打ち込むとapp/Console/Commands/配下に「GoogleDriveFileUpload.php」というファイルが作成されているはずなので、次のように変更してください。
app/Console/Commands/GoogleDriveFileUpload.php<?php namespace App\Console\Commands; use Illuminate\Console\Command; use App\Libs\GoogleDrive; class GoogleDriveFileUpload extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'google_drive:file_upload'; /** * The console command description. * * @var string */ protected $description = 'Upload file to Google Drive'; /** * @var GoogleDrive */ public $googleDrive; /** * Create a new command instance. * * @return void */ public function __construct(GoogleDrive $googleDrive) { parent::__construct(); $this->googleDrive = $googleDrive; } /** * Execute the console command. * * @return int */ public function handle() { $this->googleDrive->fileUpload(); } }Artisanコマンドを実行
$ php artisan google_drive:file_upload先ほど作成したArtisanコマンドを実行し、Googleドライブ内に「sample.jpg」というファイルがアップロードされていれば成功です。
あとがき
お疲れ様でした。基本的な流れは以上になります。あとは各自のプロジェクトに合わせてカスタマイズしてみてください。
- 投稿日:2020-12-15T00:54:34+09:00
Laravelのmodelでphp-enumをいい感じに使う
はじめに
Laravelでenumを扱おうとすると、よく出てくるのがこの三つのライブラリです。
このうち、下二つはLaravel用のライブラリなので、親和性は高いと思うのですが、他PHPフレームワークを使う時に応用が効かないかなと思って、個人的にはphp-enumを推しています。(そもそもphp-enum以外は使ったことがないので、こっちのがいいぜ!という時は教えていただけるとありがたいです)
そういう訳で、laravelでphp-enumを使う時の個人的なベストプラクティスを紹介します。
ライブラリ バージョン laravel 6.18 php-enum 1.7 やり方
enumの定義
php-enumでは、
MyCLabs\Enum\Enumクラスを継承したクラスに、定数を定義することで、num値を定義します。このような感じです。<?php declare(strict_types=1); namespace App\Enums; use MyCLabs\Enum\Enum; class RoleType extends Enum { public const SUPER_ADMIN = 'SUPER_ADMIN'; public const ADMIN = 'ADMIN'; public const USER = 'USER'; }マイグレーションの作成
マイグレーションファイルを作る時は、
Blueprintのenum()メソッドを使います。第二引数にはenumとして許可する値の配列を渡します。php-enumでは、values()メソッドでそのenum内で定義されている値の配列を得ることができるので、この値を渡します。class CreateUsersTable extends Migration { /** * Run the migrations. */ public function up(): void { Schema::create('users', function (Blueprint $table): void { $table->enum('role', RoleType::values()); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('users'); } }モデルの定義
UserモデルにRoleTypeを持たせる時、今の状態では$user->roleとしてもRoleTypeのインスタンスは取れず、DB上にあるスカラー値しか取れません。また、$user->roleにRoleTypeをそのままセットすることもできず、スカラー値に変換してからセットする必要があります。$role = RoleType::ADMIN(); $user = new User(); $user->role = $role->getValue(); $role = new Role($user->role); // NG // $user->role = $role;プロパティの値を使う度に変換を行うのは手間なので、Laravelのミューテタ1を使います。
以下のようにgetRoleAttribute〇〇(),setRoleAttribute〇〇()メソッドを定義しておくことで、laravelがよしなに変換をしてくれます。<?php declare(strict_types=1); namespace App\Models; use App\Enums\AnaUserRole; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { /** * The attributes that are mass assignable. * * @var array */ protected $fillable = ['role']; public function getRoleAttribute(string $value): RoleType { return new Role($value); } public function setRoleAttribute(RoleType $role): void { $this->attributes['role'] = $role->getValue(); } }このようにかけます。
$role = RoleType::ADMIN(); $user = new User(); $user->role = $role; // $roleはRoleTypeのインスタンス $role = $user->role;以上!
参考文献














