- 投稿日:2020-08-17T23:00:42+09:00
【PHP】Herokuへデプロイしたアプリの投稿機能で500エラー
アカウント登録からデプロイまでとても簡単なHerokuでしたが、
作成したアプリケーションの記事投稿機能でPOSTする時に500エラーを出してしまったので、
対処する時に行った事を備忘録として残しておく。環境
PHP 7.3.8
Laravel 6.18.35
DBはPostgreSQL
Heroku CLI導入済み①ログを確認する
heroku logs
コマンドでログを確認してみる
or
HerokuダッシュボードのMore
→View logs
からでもログが確認可能です。
ただこの時点ではstatus=500
が発生した事以上の情報が得られないので、
詳細な情報を出力する為にHerokuの環境変数にLOG_CHANNEL=errorlog
を設定します。ここでエラーが発見出来れば良いのですが、今回はエラーが検知出来なかったので、
次の手を考えます。②デバッグモードにしてみる
デバッグモードを
true
にしてプッシュapp.php/* |-------------------------------------------------------------------------- | Application Debug Mode |-------------------------------------------------------------------------- | | When your application is in debug mode, detailed error messages with | stack traces will be shown on every error that occurs within your | application. If disabled, a simple generic error page is shown. | */ 'debug' => env('APP_DEBUG', true),③調査結果から
結論、
GD
またはImageMagick
の導入が必要ということが分かった。composer.json"require": { 省略 "ext-gd": "*" },無事にHeroku環境で投稿機能が動作するようになりましたとさ。
- 投稿日:2020-08-17T21:51:55+09:00
パスワードのハッシュ化と比較
ハッシュ化する時→password_hash ハッシュ化していないパスワードとハッシュ化後のパスワードが同値か調べる→password_verify
- 投稿日:2020-08-17T21:49:27+09:00
【PHPでECサイト】
- 投稿日:2020-08-17T21:38:16+09:00
php artisan serveをしたらエラーになった
laravelで作成したプロジェクトでサーバを起動しようとしたところこのようなエラーがでた
Version
・PHP 7.4.9
・Composer 2.0.0-alpha3
・Laravel Installer 3.2.0$php artisan serve Laravel development server started: http://127.0.0.1:8000 Sun Aug 16 19:18:09 2020 (10355): Fatal Error Unable to create lock file: Bad file descriptor (9) Laravel development server started: http://127.0.0.1:8001 Sun Aug 16 19:18:09 2020 (10356): Fatal Error Unable to create lock file: Bad file descriptor (9) Laravel development server started: http://127.0.0.1:8002 Sun Aug 16 19:18:09 2020 (10357): Fatal Error Unable to create lock file: Bad file descriptor (9) Laravel development server started: http://127.0.0.1:8003 Sun Aug 16 19:18:09 2020 (10358): Fatal Error Unable to create lock file: Bad file descriptor (9) Laravel development server started: http://127.0.0.1:8004 Sun Aug 16 19:18:09 2020 (10359): Fatal Error Unable to create lock file: Bad file descriptor (9) Laravel development server started: http://127.0.0.1:8005 Sun Aug 16 19:18:09 2020 (10360): Fatal Error Unable to create lock file: Bad file descriptor (9) Laravel development server started: http://127.0.0.1:8006 Sun Aug 16 19:18:09 2020 (10361): Fatal Error Unable to create lock file: Bad file descriptor (9) Laravel development server started: http://127.0.0.1:8007 Sun Aug 16 19:18:09 2020 (10362): Fatal Error Unable to create lock file: Bad file descriptor (9) Laravel development server started: http://127.0.0.1:8008 Sun Aug 16 19:18:09 2020 (10363): Fatal Error Unable to create lock file: Bad file descriptor (9) Laravel development server started: http://127.0.0.1:8009 Sun Aug 16 19:18:09 2020 (10364): Fatal Error Unable to create lock file: Bad file descriptor (9) Laravel development server started: http://127.0.0.1:8010 Sun Aug 16 19:18:09 2020 (10365): Fatal Error Unable to create lock file: Bad file descriptor (9)ロックファイルが作成できないらしい。
↓この人と同じ現象だったので、ルートのtmpディレクトリに書き込みの権限を与えたところ解決した。
https://stackoverflow.com/questions/39098717/fatal-error-unable-to-create-lock-file-bad-file-descriptor-9-while-running
- 投稿日:2020-08-17T21:35:30+09:00
引数の記法
引数のデフォルト値
cal1.phpfunction sum(float $a = 5, float $b = 2) : float { return $a + $b; } print sum(); // 7 print sum(1); // 3 $aに1が入る print sum(1,5); // 6 $aに1,$bに5が入る省略できるのは後ろの引数のみなので、要注意
引数にデフォルト値を設定できるのは、後方にデフォルト値のない引数がない場合のみcal2.phpfunction sum(float $a = 5, float $b) : float { return $a + $b; } print sum(5); // Error可変長引数の関数
可変長引数の関数 = 引数の個数があらかじめ決まっていない関数
引数の前に「...」をつける
cal3.phpfunction sum(float ...$a) : float { $result = 0; foreach($a as $val){ $result += $val; } return $result; } print sum(5,3,1); // 9 print sum(5,3,1,2); // 11可変長引数は通常の引数と混合して使うことが可能。
この場合、可変長引数は引数リストの末尾に置かなければならない参考文献
- 投稿日:2020-08-17T20:50:14+09:00
type="file"で送信したファイル名の取得方法
$_FILES(HTTP ファイルアップロード変数)を使用し、取得可能
$_FILES['inputで指定したname']['name']使用例
<html> <form name="register_form" method="post" enctype="multipart/form-data"> <input type="file" name="file"> <input type="submit" value="確認"></p> </form> <html> <?php $file_name = $_FILES['file']['name']; ?>
- 投稿日:2020-08-17T20:04:48+09:00
パッケージというかnamespaceの依存関係を機械的にチェックしたい
作った。
https://github.com/nishphp/phpstan-namespace-dependency
https://github.com/nishimura/phpstan-namespace-dependency-sampleDDDでもMVCでも単純なレイヤー構造でもいいけど、各レイヤーごとの依存関係を静的にチェックしたかった。
コードを書き始めて、最初のうちは気を付けるしnewするときは割と意識しているんだけど、しばらく経った後とか急いでいるときとかにreturnやthrowでレイヤーを飛び越えて返却してしまうことがある。
最初はnamespaceを飛び越えてないかだけチェックしようと思ったけど、クラス名までチェックするようにしたらfriendやpackage private的なものも出来るようになってしまった。
コンストラクタやメソッドだけに制限をかけているわけではなくてクラス名単位で制限がかかるからちょっと違うけど。実際のプロジェクトに適用するところまでいけてないので、使い勝手はまだ不明。
- 投稿日:2020-08-17T18:54:51+09:00
日本語諸方言コーパスをDB化して遊ぶ (8) ファイル形式変換機能をつける
連載記事です。前回までに談話・話者ごとの発話総覧を作ったので、今回は最後の機能「サイト上での Excel と TextGrid の相互変換」を実装します。完全に自分用の作業メモで、説明もいろいろ足りていないと思いますが、ご容赦ください。
先述のとおり、今回は既に TextGrid と Excel を変換する Python スクリプトが存在しているので(内容には詳しく触れません)、これを Laravel アプリに組み込んでサーバー上で実行することを目指します。
- 第1回: 日本語諸方言コーパスをDB化して遊ぶ (1) 構成を考える
- 第2回: 日本語諸方言コーパスをDB化して遊ぶ (2) SQLite3 で DB 化
- 第3回: 日本語諸方言コーパスをDB化して遊ぶ (3) PHP Laravel で操作する
- 第4回: 日本語諸方言コーパスをDB化して遊ぶ (4) サービスの全体像を決める
- 第5回: 日本語諸方言コーパスをDB化して遊ぶ (5) データベースの移行とモデルの作成
- 第6回: 日本語諸方言コーパスをDB化して遊ぶ (6) 談話ごとの発話総覧を作る
- 第7回: 日本語諸方言コーパスをDB化して遊ぶ (7) 話者ごとの発話総覧を作る
- 第8回: 日本語諸方言コーパスをDB化して遊ぶ (8) ファイル形式変換機能をつける ←今ここ
- 第9回: 日本語諸方言コーパスをDB化して遊ぶ (9) Heroku でデプロイする
事前準備
今回はサーバー上のストレージにファイルを保存し、それを変換し、ダウンロードする仕組みを作るので、最初にそのあたりの設定をしておきます。アップロードしたファイルは
storage
フォルダに保存されますが、一般公開されるのはpublic
フォルダなので、慣例に従いpublic/storage
からstorage/app/public
へシンボリックリンクを張ります。下記の artisan コマンドで勝手に貼ってくれます1。cmdphp artisan storage:link画面遷移図
画面遷移図を再掲します。1ページですけど。
コンポーネントのルーティング
コンポーネントがひとつしかないので、特に解説することはありません。
resources/js/app.js+ import ConvertComponent from "./components/ConvertComponent"; + { + path: "/convert", + name: "convert", + component: ConvertComponent + }コンポーネントの作成
1画面ではありますが、たくさん機能をつけるので前回までよりは複雑です。
resouces/js/components/ConvertComponent.vue<template> <div> <form enctype="multipart/form-data"> <input type="file" name="file" id="fileRef" style="display: none" @change="fileSelected" /> <div class="input-group"> <input type="text" id="fileShow" class="form-control" placeholder="select file..." readonly /> <div class="input-group-append"> <span class="input-group-btn"> <button type="button" class="btn btn-outline-success" onclick="fileRef.click()" > Browse </button> </span> <button type="button" class="btn btn-success" @click="fileUpload" > Upload </button> </div> </div> </form> <div class="pt-3"> <table class="table table-sm table-striped"> <thead> <tr class="thead-dark"> <th colspan="2"> <div class="text-center">ファイル一覧</div> </th> </tr> </thead> <tbody> <tr v-for="file of files" v-bind:key="file.name"> <td> <span class="pl-3">{{ file.replace("public/", "") }}</span> </td> <td> <div class="text-right"> <span class="btn btn-success btn-sm" @click="toTextgrid(file)" v-if="file.indexOf('.xls') != -1" > to TextGrid </span> <span class="btn btn-outline-success btn-sm disabled" v-else > to TextGrid </span> <span class="btn btn-success btn-sm" @click="toExcel(file)" v-if="file.indexOf('.txt') != -1 || file.indexOf('.TextGrid') != -1" > to Excel </span> <span class="btn btn-outline-success btn-sm disabled" v-else > to Excel </span> <a v-bind:href="'./storage' + file.replace('public', '')" v-bind:download="file.replace('public', '')" > <span class="btn btn-warning btn-sm"> download </span> </a> <span class="btn btn-danger btn-sm" @click="deleteFile(file)" > delete </span> </div> </td> </tr> </tbody> </table> </div> </div> </template> <script> export default { data: function() { return { files: [], uploadingFileInfo: "" }; }, methods: { fileSelected(event) { this.uploadingFileInfo = event.target.files[0]; fileShow.value = fileRef.value.replace("C:\\fakepath\\", ""); }, fileUpload() { if (this.uploadingFileInfo) { const formData = new FormData(); formData.append("file", this.uploadingFileInfo); axios.post("/api/toolkit/upload", formData).then(res => { fileRef.value = ""; fileShow.value = ""; this.uploadingFileInfo = ""; this.getFileList(); }); } else { alert("アップロードするファイルを選択してください"); } }, getFileList() { axios.get("/api/convert/files").then(res => { this.files = res.data; }); }, to_textgrid(path) { axios.post("/api/convert/toTextgrid", { filepath: path }).then(() => { this.getFileList(); }); }, to_excel(path) { axios.post("/api/convert/toExcel", { filepath: path }).then(() => { this.getFileList(); }); }, deleteFile(path) { axios.post("/api/convert/delete", { filepath: path }).then(() => { this.getFileList(); }); } }, mounted() { this.getFileList(); } }; </script>ファイル選択フォーム
file
フォームは bootstrap のみではあまりいい感じになりません。いくつか簡便な手法が考案されていますが、今回は以下のサイトを参考にしました。変換ボタン
[to TextGrid] や [to Excel] のボタンはファイルの拡張子によって切り替えて、適切な拡張子のときのみ、クリックで発火するようにしています。TextGrid とかいう形式があるせいで
mimetype
による場合分けが使えないので、単純にファイル名に.txt
や.TextGrid
などの文字列が含まれるかどうかで場合分けしています2。場合分け自体はv-if
・v-else
でパパっと。<!-- .txt/.TextGrid なら上の有効ボタンを表示する --> <span class="btn btn-success btn-sm" @click="toExcel(file)" v-if="file.indexOf('.txt') != -1 || file.indexOf('.TextGrid') != -1" > to Excel </span> <!-- そうでないなら下の無効ボタンを表示する --> <span class="btn btn-outline-success btn-sm disabled" v-else > to Excel </span>ダウンロードボタン
各種変換や削除はクリックで関数を実行するようにしていますが、ダウンロードだけはファイルに直接リンクを貼っています。Laravel のサーバーからファイルをダウンロードする方法はいくつかあるのですが、
Storage
ファサードやresponse()
を使った手法はどうもうまくいかなかったので(数敗)3、直接リンクを貼る方法を採用しました。ファイルパスは後述のようなシンプルな方法で取得すると
/storage/app
以下のパスを返す(=/public/filename.ext
のようなパスが返る)ので、適当に置換してシンボリックリンク先の(/public)/storage/filename.ext
に直接リンクを貼ります。downloadリンク<a v-bind:href="'./storage' + file.replace('public', '')" v-bind:download="file.replace('public', '')" > <span class="btn btn-warning btn-sm"> download </span> </a>コントローラへのルーティング
すべて
FileController
に実装するので、関数名を適当に考えてルーティングをapi.php
に書いておきます。routes/api.php+ Route::get('/convert/files', 'FileController@getFileList'); + Route::post('/convert/upload', 'FileController@upload'); + Route::post('/convert/e_t', 'FileController@toTextgrid'); + Route::post('/convert/t_e', 'FileController@toExcel'); + Route::post('/convert/delete', 'FileController@deleteFile');
コントローラの作成
先ほど使用することにした5つの機能を実装します。
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\Storage; class FileController extends Controller{ // ファイルをアップロードして保存する public function upload(Request $request){ $filename = $request->file('file')->getClientOriginalName(); $request->file('file')->storeAs('public/',$filename); } // Excel を TextGrid に変換して保存 public function toTextgrid(Request $request) { exec("which python", $pythonpath); $scriptpath = app_path('Python/excel_to_textgrid.py'); $filepath = storage_path('app/' . $request->input('filepath')); $command = $pythonpath[0] . ' ' . $scriptpath . ' ' . $filepath; exec($command); } // TextGrid を Excel に変換して保存;上とほぼ同じ public function toExcel(Request $request) { exec("which python", $pythonpath); $scriptpath = app_path('Python/textgrid_to_excel.py'); $filepath = storage_path('app/' . $request->input('filepath')); $command = $pythonpath[0] . ' ' . $scriptpath . ' ' . $filepath; exec($command); } // ファイルのリストを取得する public function getFileList(){ // true は .gitignore などの dotfile を除外する $files = Storage::allfiles('public/', true); // SplFileInfo 型は javascript 上で扱いにくいので、ファイルパス文字列にして返す(悪?) $filepaths = explode('#', implode('#', $files)); return $filepaths; } // ファイルを削除する public function deleteFile(Request $request){ $filepath = $request->input('filepath'); Storage::delete($filepath); } }Python スクリプトの実行
サーバーに Python がインストールされていさえすれば、PHP の
exec
コマンドで Python を動かすことができます。後述しますが、Heroku ビルド時に Python と使用するモジュールを忘れずにインストールしておきましょう。Heroku は Linux 系なので4、Linux コマンドを意識して書いていきます。今回はコンテナ仮想化などはせず Windows10 で開発しましたが、本記事で扱うのはちょっとしたものなので、大きな問題はありませんでした。
実行までの手順はシンプルです。今回使用するスクリプトは「対象ファイルのパスを与えると、そのファイルを変換して、同ディレクトリに保存する」ものですので、Python 実行ファイルのパス・スクリプトのパス・対象ファイルのパスを取得して、それをもとにコマンドを組み立てるだけです。今回、スクリプトは
/app/Python
下に入れてあるので、app_path
などのパスヘルパを使用して無難にパスを取得します(ヘルパを使わないとルートのずれに対して不安定になる)。<?php // Excel を TextGrid に変換して保存 public function toTextgrid(Request $request) { // 実行環境での python へのパスを取得 // Windows cmd なら exec("where python", $pythonpath); exec("which python", $pythonpath); // 実行したい python スクリプトのパスを取得 $scriptpath = app_path('Python/excel_to_textgrid.py'); // POST されてきた filepath を取得して適切な相対パスに変換 $filepath = storage_path('app/' . $request->input('filepath')); // コマンドを組み立てて実行 // 環境に複数バージョンの Python がある場合は index に注意 $command = $pythonpath[0] . ' ' . $scriptpath . ' ' . $filepath; exec($command); }なお、ここで使用したスクリプトは入力ファイルと同じディレクトリに出力ファイルを保存する設定になっています。
完成図
こんな感じになっているはずです。
改善点
既に言及したエラーハンドリングなどはもとより、セキュリティ上は
exec
が大きな問題です。一般にユーザーが改竄できるデータを PHP のexec
関数にそのままぶち込むのは大変危険ですので、適切にエスケープする必要があります。今回はいちおう Laravel のパスヘルパを通しているので大丈夫じゃないかな、と思いますが、ヘルパの正確な挙動を把握していない限りは万全を期したほうがよいでしょう。次回
Heroku に上げていきます(最終回)。
後述しますが、ローカルで張ったシンボリックリンクが Heroku 上で勝手に張られることはないので、必要な命令を
composer.json
に書き込んでおいて、ビルド時にシンボリックリンクが張られるようにしておく必要があります。 ↩ほんとうはこんなフロントエンドのなんちゃって検証ではなく、サーバ側でちゃんと入力ファイルを検証しないとダメ。 ↩
パス解決に失敗したり、403エラーが出たり、POSTレスポンスにファイル内容は積まれてくるけどダウンロードまで行けなかったりしました。 ↩
Heroku では Dyno という、Amazon EC2 の巨大インスタンス上で動作する軽量 Linux コンテナが使われています。 ↩
- 投稿日:2020-08-17T18:20:29+09:00
【PHPでECサイト③】カート機能
はじめに
今回は、カート機能を実装していきます。
※当ページは、【PHPでECサイト】で作られたものを前提にしています。
- 【PHPでECサイト①】CRUD機能
- 【PHPでECサイト②】ログイン機能バージョン
PHP:7.4.5
phpMyAdmin:5.0.2
MySQL:5.7.30今回作成するファイル
html
- index.php
- index_add_cart.php
- cart.php
- cart_delete.php
- finish.phpmodel
- carts.phpview
- index_view.php
- cart_view.php
- finish_view.phpテーブルの作成
sample_cartsCREATE TABLE `sample_carts` ( `cart_id` int(11) NOT NULL, `user_id` int(11) NOT NULL, `item_id` int(11) NOT NULL, `amount` int(11) NOT NULL, `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ALTER TABLE `sample_carts` MODIFY `cart_id` int(11) NOT NULL AUTO_INCREMENT, ADD PRIMARY KEY (`cart_id`), ADD KEY `item_id` (`item_id`), ADD KEY `user_id` (`user_id`);定義
const.phpdefine('INDEX_URL', '/index.php'); define('CART_URL', '/cart.php'); define('FINISH_URL', '/finish.php');Viewの作成
index_view.php<!DOCTYPE html> <html lang="ja"> <head> <meta chartset="UTF-8"> <title>商品一覧</title> </head> <body> <h1>商品一覧</h1> <p>ようこそ、<?php print($user['user_name']); ?>さん。</p> <a href="<?php print(ADMIN_URL);?>">商品管理</a> <a href="<?php print(CART_URL);?>">カート</a> <a href="<?php print(LOGOUT_URL);?>">ログアウト</a> <!-- メッセージ・エラーメッセージ --> <?php include VIEW_PATH . 'templates/messages.php'; ?> <!-- 商品一覧 --> <?php foreach($items as $item){ ?> <div> <?php print($item['name']); ?> <?php print($item['price']); ?> <!-- 売り切れの場合は、formを置換 --> <?php if($item['stock'] > 0){ ?> <form method="post" action="index_add_cart.php"> <input type="submit" value="カートに追加"> <input type="hidden" name="item_id" value="<?php print($item['item_id']) ?>"> </form> <?php }else{ ?> <p>現在、売り切れです。</p> <?php } ?> </div> <?php } ?> </body> </html>cart_view.php<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>カート</title> </head> <body> <h1>商品一覧</h1> <p>ようこそ、<?php print($user['user_name']); ?>さん。</p> <a href="<?php print(ADMIN_URL);?>">商品管理</a> <a href="<?php print(INDEX_URL);?>">商品一覧</a> <a href="<?php print(LOGOUT_URL);?>">ログアウト</a> <!-- メッセージ・エラーメッセージ --> <?php include VIEW_PATH . 'templates/messages.php'; ?> <!-- カート一覧 --> <?php if(count($carts) > 0){ ?> <table> <thead> <tr> <th>商品名</th> <th>価格</th> <th>数量</th> <th>小計</th> <th>操作</th> </tr> </thead> <tbody> <?php foreach($carts as $cart){ ?> <tr> <td><?php print($cart['name']); ?></td> <td><?php print($cart['price']); ?></td> <td><?php print($cart['amount']); ?></td> <td><?php print($cart['price'] * $cart['amount']); ?></td> <td> <form method="post" action="cart_delete.php"> <input type="submit" value="削除"> <input type="hidden" name="cart_id" value="<?php print($cart['cart_id']); ?>"> </form> </td> </tr> <?php } ?> </tbody> </table> <p>合計金額:<?php print($total_price); ?></p> <form method="post" action="finish.php"> <input type="submit" value="購入する"> </form> <?php }else{ ?> <p>カートに商品はありません。</p> <?php } ?> </body> </html>finish_view.php<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>ご購入ありがとうございました!</title> </head> <body> <h1>ご購入ありがとうございました!</h1> <!-- メッセージ・エラーメッセージ --> <?php include VIEW_PATH . 'templates/messages.php'; ?> <!-- 購入した商品 --> <?php if(count($carts) > 0){ ?> <table> <thead> <tr> <th>商品名</th> <th>価格</th> <th>購入数</th> <th>小計</th> </tr> </thead> <tbody> <?php foreach($carts as $cart){ ?> <tr> <td><?php print($cart['name']); ?></td> <td><?php print($cart['price']); ?></td> <td><?php print($cart['amount']); ?></td> <td><?php print($cart['price'] * $cart['amount']); ?></td> </tr> <?php } ?> </tbody> </table> <p>合計金額:<?php print($total_price); ?></p> <?php }else{ ?> <p>カートに商品はありません。</p> <?php } ?> </body> </html>カートに追加
Model
carts.php<?php require_once MODEL_PATH . 'functions.php'; require_once MODEL_PATH . 'db.php'; // カートに追加するために必要なデータ function get_user_cart($db, $user_id, $item_id){ $sql = " SELECT sample_items.item_id, sample_items.name, sample_items.price, sample_items.stock, sample_carts.cart_id, sample_carts.user_id, sample_carts.amount FROM sample_carts JOIN sample_items ON sample_carts.item_id = sample_items.item_id WHERE sample_carts.user_id = ? AND sample_items.item_id = ? "; return fetch_query($db, $sql, array($user_id, $item_id)); } // カートに追加(既に同じ商品があれば、個数のみUpdate) function add_cart($db, $user_id, $item_id ) { $cart = get_user_cart($db, $user_id, $item_id); if($cart === false){ return insert_cart($db, $user_id, $item_id); } return update_cart_amount($db, $cart['cart_id'], $cart['amount'] + 1); } function insert_cart($db, $user_id, $item_id, $amount = 1){ $sql = " INSERT INTO sample_carts( item_id, user_id, amount ) VALUES(?,?,?) "; return execute_query($db, $sql, array($item_id, $user_id, $amount)); } function update_cart_amount($db, $cart_id, $amount){ $sql = " UPDATE sample_carts SET amount = ? WHERE cart_id = ? LIMIT 1 "; return execute_query($db, $sql, array($amount, $cart_id)); }Controller
index.php<?php require_once '../conf/const.php'; require_once MODEL_PATH. 'functions.php'; require_once MODEL_PATH. 'users.php'; require_once MODEL_PATH. 'items.php'; session_start(); if(is_logined() === false){ redirect_to(LOGIN_URL); } $db = get_db_connect(); $user = get_login_user($db); $items = get_items($db); include_once VIEW_PATH. 'index_view.php';index_add_cart.php<?php require_once '../conf/const.php'; require_once MODEL_PATH. 'functions.php'; require_once MODEL_PATH. 'users.php'; require_once MODEL_PATH. 'items.php'; require_once MODEL_PATH. 'cart.php'; session_start(); if(is_logined() === false){ redirect_to(LOGIN_URL); } $db = get_db_connect(); $user = get_login_user($db); $item_id = get_post('item_id'); // カートに追加 if(add_cart($db, $user['user_id'], $item_id)){ set_message('カートに商品を追加しました。'); } else{ set_error('カートの更新に失敗しました。'); } redirect_to(INDEX_URL);カートの商品を購入
Model
functions.phpfunction has_error(){ return isset($_SESSION['__errors']) && count($_SESSION['__errors']) !== 0; }carts.php// カートの商品データ function get_user_carts($db, $user_id){ $sql = " SELECT sample_items.item_id, sample_items.name, sample_items.price, sample_items.stock, sample_carts.cart_id, sample_carts.user_id, sample_carts.amount FROM sample_carts JOIN sample_items ON sample_carts.item_id = sample_items.item_id WHERE sample_carts.user_id = ? "; return fetch_all_query($db, $sql, array($user_id)); } // カートの商品の合計額 function sum_carts($carts){ $total_price = 0; foreach($carts as $cart){ $total_price += $cart['price'] * $cart['amount']; } return $total_price; } // 購入処理 function purchase_carts($db, $carts){ if(validate_cart_purchase($carts) === false){ return false; } // 購入後、カートの中身削除&在庫変動 $db->beginTransaction(); try { foreach($carts as $cart){ if(update_stock($db, $cart['item_id'], $cart['stock'] - $cart['amount']) === false){ set_error($cart['name'] . 'の購入に失敗しました。'); } } delete_user_carts($db, $carts[0]['user_id']); $db->commit(); }catch(PDOException $e){ $db->rollback(); throw $e; } } function delete_user_carts($db, $user_id){ $sql = " DELETE FROM sample_carts WHERE user_id = ? "; execute_query($db, $sql, array($user_id)); } // バリデーション function validate_cart_purchase($carts){ if(count($carts) === 0){ set_error('カートに商品が入っていません。'); return false; } foreach($carts as $cart){ if($cart['stock'] - $cart['amount'] < 0){ set_error($cart['name'] . 'は在庫が足りません。購入可能数:' . $cart['stock']); } } if(has_error() === true){ return false; } return true; }Controller
cart.php<?php require_once '../conf/const.php'; require_once MODEL_PATH . 'functions.php'; require_once MODEL_PATH . 'users.php'; require_once MODEL_PATH . 'items.php'; require_once MODEL_PATH . 'carts.php'; session_start(); if(is_logined() === false){ redirect_to(LOGIN_URL); } $db = get_db_connect(); $user = get_login_user($db); $carts = get_user_carts($db, $user['user_id']); $total_price = sum_carts($carts); require_once VIEW_PATH. 'cart_view.php';finish.php<?php require_once '../conf/const.php'; require_once MODEL_PATH . 'functions.php'; require_once MODEL_PATH . 'users.php'; require_once MODEL_PATH . 'items.php'; require_once MODEL_PATH . 'carts.php'; session_start(); if(is_logined() === false){ redirect_to(LOGIN_URL); } $db = get_db_connect(); $user = get_login_user($db); $carts = get_user_carts($db, $user['user_id']); $total_price = sum_carts($carts); // 購入処理 if(purchase_carts($db, $carts) === false){ set_error('商品が購入できませんでした。'); redirect_to(CART_URL); } include_once VIEW_PATH. 'finish_view.php';カートの商品を削除
Model
carts.php// カートの商品の削除 function delete_cart($db, $cart_id){ $sql = " DELETE FROM sample_carts WHERE cart_id = ? LIMIT 1 "; return execute_query($db, $sql, array($cart_id)); }Controller
cart_delete.php<?php require_once '../conf/const.php'; require_once MODEL_PATH . 'functions.php'; require_once MODEL_PATH . 'users.php'; require_once MODEL_PATH . 'items.php'; require_once MODEL_PATH . 'carts.php'; session_start(); if(is_logined() === false){ redirect_to(LOGIN_URL); } $db = get_db_connect(); $user = get_login_user($db); $cart_id = get_post('cart_id'); if(delete_cart($db, $cart_id)){ set_message('カートの商品を削除しました。'); } else { set_error('カートの商品の削除に失敗しました。'); } redirect_to(CART_URL);
- 投稿日:2020-08-17T17:51:45+09:00
phpUnitで配列の値を順不同で比較したい時
assertSameだと順不同で比較してくれない。
そういう時はassertEqualsCanonicalizingを使えば解決。
phpUnit7.5以降のversionで使用することができます。使用例//成功例。配列を順不同で比較してくれる。 $this->assertEqualsCanonicalizing([1,2,3,4],[4,1,3,2]); //失敗例。他の関数と同じく第3引数でコメントしてくれる $this->assertEqualsCanonicalizing([1,2,3,4],[4,1,3,3],"失敗しました");参考:https://www.geeksforgeeks.org/phpunit-assertequalscanonicalizing-function/
- 投稿日:2020-08-17T17:15:05+09:00
Laravelでmailgunを使いメール送信してみる
mailgunアカウント登録
mailgunのAPIを使うにはアカウント登録が必要です。
公式ページにアクセスし「Start Sending」をクリックしてください。アカウント登録ページが表示されます。
必要情報を入力してください。
「Add payment info now」のチェックを外しておけば、クレジットカード情報は入力しなくて済みます。
※後から有償版に切り替えたい場合は、ダッシュボードの「Account Settings」>「Billing plan」から切り替えることができます。アカウントを登録すると、公式からメールが2通届きます。
「Hi [登録者名], please verify your Mailgun account」のメールの方に記載されているURLをクリックしてメール認証を行います。次に認証コードの登録を行います。
画像をなくしてしまったのですが、メール認証を行うと携帯番号を入力する画面が表示され、電話番号の入力を求められます。番号を入力して認証コードの送信依頼を行うと、番号を入力した端末に認証コードが送信されます。
送信された認証コードを、「Verlflcation Code」に入力して「Validate」を押してください。認証に成功してダッシュボード画面が表示されればアカウント登録は完了です。
Laravelの下準備
Guzzle HTTPライブラリを入れる
以下のコマンドを実行します。
composer require guzzlehttp/guzzle.envファイルに設定を記述
ドメインの確認
ダッシュボード画面から「Sending」>「Domains」をクリックします。
「Name」以下に表示されているものがドメインとなります。API keyの確認
「Name」のドメインをクリックし、APIの「Select」>「cURL」をクリックするとAPI keyを確認することができます。
.envに記述
- MAIL_DRIVER
mailgun
と記述します- MAILGUN_SECRET
- ドメインを記述します
- MAILGUN_SECRET
- API keyを記述します
※API keyやドメインをコピペするときに、最後に半角スペースなどが入らないように気を付けてください。
MAIL_DRIVER=mailgun MAILGUN_DOMAIN=YOUR_MAILGUN_DOMAIN MAILGUN_SECRET=YOUR_SECRET_KEYこれでLaravelの設定は完了です。
メール受信設定
ダッシュボード画面から「Sending」>「Domains」をクリックし、「Authorized Recipients」の「Email address」に受信するメールアドレスを入力して「Save Recipient」を押します。
入力したメールアドレス宛に「Would you like to receive emails from sankosc on Mailgun?」というタイトルのメールが届くので、「I Agree 」を押します。
Confirm画面が表示されるので「Yes」を押します。
Successと表示されれば受信設定は完了です。
メール送信
実装
以下のコマンドを実行します。
php artisan make:mail MailgunTest
app/Mail
以下に生成されたMailgunTest.php
を編集していきます。<?php namespace App\Mail; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels; class MailgunTest extends Mailable { use Queueable, SerializesModels; /** * Create a new message instance. * * @return void */ public function __construct() { } /** * Build the message. * * @return $this */ public function build() { return $this->view('mails.mail') ->text('mails.mail') ->subject('タイトル') ->with([ 'text' => '本文', ]); } }
build
メソッド内でメールテンプレートとなるviewファイルに、メールのタイトルと本文を記述しています。次に
resources/views
以下にmailsフォルダを作成し、mail.blade.php
ファイルを追加して以下のように編集します。<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> </head> <body>{{$text}}</body> </html>送信テスト
tinkerから送信テストをしてみます。
php artisan tinkerPsy Shell v0.10.4 (PHP 7.2.15 — cli) by Justin Hileman >>> \Mail::to('送信先アドレス')->send(new App\Mail\MailgunTest()); => nullメールが届きました。
- 投稿日:2020-08-17T15:23:01+09:00
【Laravel】ユーザー認証機能である「Auth」を日本語化する方法
開発環境
PHP 7.4.8
Laravel Framework 7.23.0
目的
ユーザー認証機能である「Auth」を日本語化する
Auth日本語化
【参考記事】
https://laraweb.net/tutorial/6949/1. Laravelの日本語の設定
config/app.php#下記内容の該当箇所を書き換える 'timezone' => 'Asia/Tokyo', // タイムゾーン 'locale' => 'ja', // 第一言語を日本語(ローカライゼーション) 'fallback_locale' => 'en', // 該当言語が見つからない場合の言語 'faker_locale' => 'ja_JP',2. 新規ディレクトリ、ファイル作成
①「ja.json」ファイル作成
- resources/lang内にja.jsonファイル新規作成
- 作成後、下記内容を記述(コピペOK)
ja.json{ "Login": "ログイン", "Register": "新規登録", "Forgot Your Password?": "パスワードを忘れた場合", "Reset Password": "パスワード再設定", "Send Password Reset Link": "パスワード再設定URLを送信", "Name": "お名前", "E-Mail Address": "メールアドレス", "Password": "パスワード", "Confirm Password": "パスワード(確認用)", "Remember Me": "ログイン状態を保存", "Logout": "ログアウト", "Hello!": "ご利用ありがとうございます。", "Reset Password Notification": "パスワード再設定のお知らせ", "You are receiving this email because we received a password reset request for your account.": "あなたのアカウントでパスワード再発行のリクエストがありました。", "This password reset link will expire in :count minutes.": "再設定URLの有効期限は :count 分です。", "If you did not request a password reset, no further action is required.": "もしパスワード再発行をリクエストしていない場合、操作は不要です。", "If you’re having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser: [:actionURL](:actionURL)": "\":actionText\"ボタンを押しても何も起きない場合、以下URLをコピーしてWebブラウザに貼り付けてください。\n[:actionURL](:actionURL)", "Regards": "よろしくお願いいたします" }②「ja」ディレクトリ作成
- resources/lang内にjaディレクトリ作成
- resources/lang/enからファイルをコピーし、jaディレクトリ内に作成
resources/lang/jaauth.php pagination.php passwords.php validation.php③各ファイルの内容を変更
auth.php
auth.php<?php return [ 'failed' => 'ログインできません。入力した情報に誤りがないかご確認ください。', 'throttle' => '何度もログインに失敗したため、:seconds秒後に再度お試しください。', ];pagination.php
pagination.php<?php return [ 'previous' => '« 前へ', 'next' => '次へ »', ];passwords.php
passwords.php<?php return [ 'reset' => 'パスワードをリセットしました。', 'sent' => 'パスワードリセット用URLを送信しました。', 'token' => 'パスワードリセット用トークンが不正です。', 'user' => 'メールアドレスに一致するユーザーが見つかりません。', ];validation.php
validation.php<?php return [ /* |-------------------------------------------------------------------------- | Validation Language Lines |-------------------------------------------------------------------------- | | The following language lines contain the default error messages used by | the validator class. Some of these rules have multiple versions such | as the size rules. Feel free to tweak each of these messages here. | */ 'accepted' => ':attributeを承認してください。', 'active_url' => ':attributeに正しいURLを入力してください。', 'after' => ':attributeは:dateより先の日付を入力してください。', 'after_or_equal' => ':attributeは:date以降の日付を入力してください。', 'alpha' => ':attributeは英字で入力してください。', 'alpha_dash' => ':attributeは英数字とハイフン、アンダーバーのみで入力してください。', 'alpha_num' => ':attributeは英数字で入力してください。', 'array' => ':attributeは配列で入力してください。', 'before' => ':attributeは:dateより前の日付を入力してください。', 'before_or_equal' => ':attributeは:date以前の日付を入力してください。', 'between' => [ 'numeric' => ':attributeは:min〜:maxの範囲で入力してください。', 'file' => ':attributeは:min〜:max KBのファイルを選択してください。', 'string' => ':attributeは:min〜:max文字の範囲で入力してください。', 'array' => ':attributeは:min〜:max個の範囲内にしてください。', ], 'boolean' => ':attributeはtrueかfalseにしてください。', 'confirmed' => ':attributeが確認用と一致しません。', 'date' => ':attributeを正しい日付で入力してください。', 'date_equals' => ':attributeを:dateと一致するよう入力してください。', 'date_format' => ':attributeの書式を:formatに沿って入力してください。', 'different' => ':attributeと:otherは違うものを入力してください。', 'digits' => ':attributeは:digits桁で入力してください。', 'digits_between' => ':attributeは:min〜:max桁で入力してください。', 'dimensions' => ':attributeの画像サイズが不正です。', 'distinct' => ':attributeが重複しています。', 'email' => ':attributeを正しい形式で入力してください。', 'ends_with' => ':attributeを:valuesで終わるよう入力してください。', 'exists' => '選択した値が不正です。', 'file' => ':attributeはファイルを選択してください。', 'filled' => ':attributeを入力してください。', 'gt' => [ 'numeric' => ':attributeは:valueより多く入力してください。', 'file' => ':attributeは:value KBより大きいファイルを選択してください。', 'string' => ':attributeは:value文字より多く入力してください。', 'array' => ':attributeは:value個より多くしてください。', ], 'gte' => [ 'numeric' => ':attributeは:value以上で入力してください。', 'file' => ':attributeは:value KB以上のファイルを選択してください。', 'string' => ':attributeは:value文字以上入力してください。', 'array' => ':attributeは:value個以上にしてください。', ], 'image' => ':attributeは画像にしてください。', 'in' => ':attributeは不正です。', 'in_array' => ':attributeは:otherの範囲外です。', 'integer' => ':attributeは数字で入力してください。', 'ip' => ':attributeはIPアドレス形式で入力してください。', 'ipv4' => ':attributeはIPv4形式で入力してください。', 'ipv6' => ':attributeはIPv6形式で入力してください。', 'json' => ':attributeはJSON形式で入力してください。', 'lt' => [ 'numeric' => ':attributeは:valueより少なく入力してください。', 'file' => ':attributeは:value KBより小さいファイルを選択してください。', 'string' => ':attributeは:value文字より少なく入力してください。', 'array' => ':attributeは:value個より少なくしてください。', ], 'lte' => [ 'numeric' => ':attributeは:value以下で入力してください。', 'file' => ':attributeは:value KB以下のファイルを選択してください。', 'string' => ':attributeは:value文字以下入力してください。', 'array' => ':attributeは:value個以下にしてください。', ], 'max' => [ 'numeric' => ':attributeは:max以下で入力してください。', 'file' => ':attributeは:max KB以下のファイルを選択してください。', 'string' => ':attributeは:max文字以下入力してください。', 'array' => ':attributeは:max個以下にしてください。', ], 'mimes' => ':attributeは:values形式で選択してください。', 'mimetypes' => ':attributeは:values形式で選択してください。', 'min' => [ 'numeric' => ':attributeは:min以上で入力してください。', 'file' => ':attributeは:min KB以上のファイルを選択してください。', 'string' => ':attributeは:min文字以上入力してください。', 'array' => ':attributeは:min個以上にしてください。', ], 'not_in' => ':attributeは不正です。', 'not_regex' => ':attributeの書式が不正です。', 'numeric' => ':attributeは数字で入力してください。', 'present' => ':attributeは存在する必要があります。', 'regex' => ':attributeの書式が不正です。', 'required' => ':attributeを入力してください。', 'required_if' => ':otherが:valueの時、:attributeを入力してください。', 'required_unless' => ':otherが:valuesでない時、:attributeを入力してください。', 'required_with' => ':valuesが存在する時、:attributeを入力してください。', 'required_with_all' => ':valuesが存在する時、:attributeを入力してください。', 'required_without' => ':valuesが存在しない時、:attributeを入力してください。', 'required_without_all' => ':valuesが存在しない時、:attributeを入力してください。', 'same' => ':attributeと:otherが一致するよう入力してください。', 'size' => [ 'numeric' => ':attributeは:sizeで入力してください。', 'file' => ':attributeは:size KBのファイルを選択してください。', 'string' => ':attributeは:size文字で入力してください。', 'array' => ':attributeは:size個にしてください。', ], 'starts_with' => ':attributeを:valuesから始まるよう入力してください。', 'string' => ':attributeは門司で入力してください。', 'timezone' => ':attributeを正しいタイムゾーンで入力してください。', 'unique' => ':attributeは既に取得されているため、違うものを入力してください。', 'uploaded' => ':attributeはアップロードに失敗しました。', 'url' => ':attributeを正しいURLで入力してください。', 'uuid' => ':attributeを正しいUUIDで入力してください。', /* |-------------------------------------------------------------------------- | Custom Validation Language Lines |-------------------------------------------------------------------------- | | Here you may specify custom validation messages for attributes using the | convention "attribute.rule" to name the lines. This makes it quick to | specify a specific custom language line for a given attribute rule. | */ 'custom' => [ 'attribute-name' => [ 'rule-name' => 'custom-message', ], ], /* |-------------------------------------------------------------------------- | Custom Validation Attributes |-------------------------------------------------------------------------- | | The following language lines are used to swap our attribute placeholder | with something more reader friendly such as "E-Mail Address" instead | of "email". This simply helps us make our message more expressive. | */ 'attributes' => [ 'email' => 'メールアドレス', 'password' => 'パスワード', ], ];日本語のメッセージファイル
「Laravel7.x公式ドキュメント」
https://github.com/laravel-ja/ja-docs-7.x
「Laravel6.xLTSの公式ドキュメント」
https://github.com/laravel-ja/ja-docs-6.x
「Laravel 5.5 日本語メッセージファイル」
https://github.com/minoryorg/laravel-resources-lang-ja
- 投稿日:2020-08-17T14:54:09+09:00
SmartyファイルをTwigに変換する (EC-CUBE2 → EC-CUBE4 移行用)
テンプレートを捨てたくない
- EC-CUBE3以降、テンプレートエンジンがSmartyからTwigになった
- EC-CUBE2でデフォルトのページを使ってた人は、EC-CUBE4でもそのままデフォルトのページを使えばいいと思う
- オリジナルのページを追加して使ってた人にとっては、それをEC-CUBE4に移行してからも持ち越して使いたいことがある ( = 私)
- それが手で書き移せるような分量ではない場合もある ( = 私)
自動変換ツール
PHP Smarty to Twig Converter
sankarsuda/to-twig
toTwig is an utility to convert smarty template engine to twig template engine.
- 個人制作 (インドのSankar Sudaさんありがとう‥‥)
- 7年前に作ったまま放置されている
Converting Smarty templates to Twig
OXID-eSales/smarty-to-twig-converter
Converting tool located at GitHub allows to convert existing Smarty template files to Twig syntax. The tool besides standard Smarty syntax is adjusted to handle custom OXID modifications and extensions.
- 上記のto-twigを企業がフォークして自社サービス向けにリファクター&改造した物らしい
今もメンテされていて、PHP7.4までバッチリ対応
こちらをいじくって使っていこうと思います。
git clone https://github.com/OXID-eSales/smarty-to-twig-converter.git cd smarty-to-twig-converter
EC-CUBE2式のブラケットに対応させる
- 素のSmartyはブラケットに
{
}
を使っていますが、EC-CUBE2はこの書式を<!--{
}-->
に変更して使っています (ちなみにOXIDでは[{
}]
みたいです)。- コード内の、正規表現でブラケットをパターンマッチさせている部分をEC-CUBE2の書式に合わせて書き換えていきます。
app\toTwig\Converter\ConverterAbstract.phpprotected function getOpeningTagPattern(string $tagName): string { - return sprintf("#\[\{\s*%s\b\s*((?:(?!\[\{|\}\]).(?<!\[\{)(?<!\}\]))+)?\}\]#is", preg_quote($tagName, '#')); + return sprintf("#<!--\{\s*%s\b\s*((?:(?!<!--\{|\}-->).(?<!<!--\{)(?<!\}-->))+)?\}-->#is", preg_quote($tagName, '#')); } protected function getClosingTagPattern(string $tagName): string { - return sprintf("#\[\{\s*/%s\s*\}\]#i", preg_quote($tagName, '#')); + return sprintf("#<!--\{\s*/%s\s*\}-->#i", preg_quote($tagName, '#')); } private function convertFilters(string $string): string { return preg_replace_callback( - '/\|@?(?:\w+)(?:\:|\b)(?:"\s+"|\'\s+\'|[^\s}|])*/', + '/\|@?(?:\w+)(?:\:|\b)(?:"\s+"|\'\s+\'|[^}|])*/',app\toTwig\Converter\VariableConverter.phppublic function convert(string $content): string { - $pattern = '/\[\{([^{}]+)?\}\]/'; + $pattern = '/<!--\{([^{}]+)?\}-->/';app\toTwig\Converter\CommentConverter.phppublic function convert(string $content): string { - $pattern = '#\[\{\*((?:(?!\[\{|\}\]).(?<!\[\{)(?<!\}\]))+)?\*\}\]#is'; + $pattern = '#<!--\{\*((?:(?!<!--\{\*|\*\}-->).(?<!<!--\{\*)(?<!\*\}-->))+)?\*\}-->#is';変換 → 手動で調整
- 使い方はマニュアルの通りです。
composer install php toTwig convert --path=/path/to/dir --ext=.twig
- この自動変換は全く完璧ではありません。生成したTwigファイルをEC-CUBE4に持ってった後、間違いのない表示になるまで手直しし続けることになりました。
- それでも一から書き写すよりはずっと楽でした。終わり!
- 投稿日:2020-08-17T12:59:31+09:00
【Windows/PHP】XAMPPのsendmailでローカル環境からメールを送信する(mb_send_mail())
概要
Windowsのローカル環境からメールを送るための設定メモです。
XAMPPを利用しており、その中にあるsendmailを使います。今回はPHPの
mb_send_mail()
でメールを送るために設定します。メールサーバーの準備
メールサーバーを持ち合わせていないので、mailtrapというサービスを利用します。
無料会員登録で簡単にメールの送受信テストが行えるサービスです。
(メールサーバーを持っている場合は、そちらの情報を使えばOKです)
mailtrapについては以下も参考にして下さい。
まず、mailtrapに登録するとSMTPサーバーの情報が与えられます。
今回はこちらを使って、ローカルからメールを送信できるようにします。
sendmailの設定
XAMPPの中にsendmailのディレクトリがあります。
C:/xampp/sendmail
設定ファイル
sendmail.ini
があるので、コピーしてオリジナルを残しておきましょう。
設定ファイルを開いて、SMTPサーバーの情報を記述します。mailtrapの場合はSMTPサーバーなので、下記の設定で利用が可能です。
sendmail.ini; configuration for fake sendmail ; if this file doesn't exist, sendmail.exe will look for the settings in ; the registry, under HKLM\Software\Sendmail [sendmail] ; you must change mail.mydomain.com to your smtp server, ; or to IIS's "pickup" directory. (generally C:\Inetpub\mailroot\Pickup) ; emails delivered via IIS's pickup directory cause sendmail to ; run quicker, but you won't get error messages back to the calling ; application. smtp_server=smtp.mailtrap.io ; smtp port (normally 25) smtp_port=465 ; SMTPS (SSL) support ; auto = use SSL for port 465, otherwise try to use TLS ; ssl = alway use SSL ; tls = always use TLS ; none = never try to use SSL smtp_ssl=TLS ; if your smtp server requires authentication, modify the following two lines auth_username=メールサーバーのユーザー名(mailtrapで付与されたもの) auth_password=メールサーバーのパスワード(mailtrapで付与されたもの) : :
設定ファイルに記述するのは下記の5か所です。
記述したらsendmail.ini
を上書き保存します。smtp_server=メールサーバーのホスト名(例:smtp.mailtrap.io) smtp_port=使用するポート番号(例:465) smtp_ssl=使用する暗号化プロトコル(例:TLS) auth_username=メールサーバーのユーザー名 auth_password=メールサーバーのパスワードPHPの設定
次に、PHPにsendmailを指定する設定をします。
設定ファイルC:/xampp/php/php.ini
を編集します。
(パスは環境によって異なります。コピーしてオリジナルを保存しておきましょう。)php.iniの
sendmail_path
にsendmailの実行ファイルを指定します。
上書き保存して、Webサーバーを再起動させます。php.ini; For Unix only. You may supply arguments as well (default: "sendmail -t -i"). ; http://php.net/sendmail-path ;sendmail_path = sendmail_path =""C:\xampp\sendmail\sendmail.exe" -t"
これで設定は完了です。参考
まとめ
以上の設定で、PHPで
mb_send_mail()
を使ってメールを送ることができます。
mailtrapは送信したメールが全てmailtrapの受信箱に入るので、指定の送信先では受信されません。テストメールが自分の受信箱に溜まることがないのでとても便利です。
(送信先をミスっても相手には届かないので、開発中は安心)これで心置きなくメールテストを行いたいと思います。以上!
- 投稿日:2020-08-17T12:23:20+09:00
【PHP】容量が大きくアップロードが中断される、タイムアウトする際の対処法
はじめに
業務上でPHPファイルがインポートできなかった際に調べたサイト(ページ下部リンク参照)を参考にしています。
素人の備忘録程度の投稿なので、至らない点があると思いますがご了承ください①容量が大きくアップロードが中断される場合
容量が大きいファイルの場合、
php.ini
というファイルを書き換える必要があります。
アップロードされるファイルサイズの上限が決まっているので、php.iniファイル内の下記を修正します。・memory_limit(使用できるメモリの上限設定)
・post_max_size(POSTされるデータの上限設定)
・upload_max_filesize(アップロードされるファイルサイズの上限設定)
php.inimemory_limit = 128M post_max_size = 8M upload_max_filesize = 2M例えばこのような設定になっている場合
php.inimemory_limit = 128M post_max_size = 128M upload_max_filesize = 128M全体的に値を大きくすることでファイル上限を上げ、アップロードできるようになります。
②アップロードしようとするとタイムアウトする場合
容量の大きいファイルをアップロードするに伴い、アップロードに時間がかかるためタイムアウトしてしまう可能性があります。
その場合、config.default.php
というファイルを書き換えることで問題を解消できます。
config.default.php$cfg[‘ExecTimeLimit’] = 300;例えばこのような設定になっている場合、時間制限が300秒となります。
config.default.php$cfg[‘ExecTimeLimit’] = 0;数字を0にすることで、時間が無制限になりタイムアウトしなくなります。
参考にさせていただいたサイト
- 投稿日:2020-08-17T10:09:09+09:00
$ git clone で取得したLaravelアプリのローカルサーバの起動に失敗する
目的
- 記事名のエラーを解決した話をまとめる
補足情報
- 本記事は一年以上前に記載した物が下書きボックスに眠っていた内容である。供養の意味も含めて一応記事にする。
問題発生までの経緯
- GitHubのリモートリポジトリを
$ git clone
コマンドを用いて取得した。- .envファイルを作成、自分の環境にあった内容を記載した。
Laravelアプリ名ディレクトリで下記コマンドを実行してローカル開発環境用のサーバを起動した。
$ php artisan serve問題
コマンド
$ php artisan serve
を実行後、下記のエラーが出力された。php artisan serve PHP Warning: require(/Users/ookawashun/workspace/work/laravel/calculation_drill_app/vendor/autoload.php): failed to open stream: No such file or directory in /Users/ookawashun/workspace/work/laravel/calculation_drill_app/artisan on line 18 Warning: require(/Users/ookawashun/workspace/work/laravel/calculation_drill_app/vendor/autoload.php): failed to open stream: No such file or directory in /Users/ookawashun/workspace/work/laravel/calculation_drill_app/artisan on line 18 PHP Fatal error: require(): Failed opening required '/Users/ookawashun/workspace/work/laravel/calculation_drill_app/vendor/autoload.php' (include_path='.:/usr/local/Cellar/php/7.4.3/share/php/pear') in /Users/ookawashun/workspace/work/laravel/calculation_drill_app/artisan on line 18 Fatal error: require(): Failed opening required '/Users/ookawashun/workspace/work/laravel/calculation_drill_app/vendor/autoload.php' (include_path='.:/usr/local/Cellar/php/7.4.3/share/php/pear') in /Users/ookawashun/workspace/work/laravel/calculation_drill_app/artisan on line 18
問題解決までの経緯
アプリ名ディレクトリで下記コマンドを実行してcomposerのパッケージインストールを行った。
$ composer install再度ローカルサーバを起動するコマンドを実行したら正常にローカルサーバが起動した。
- 投稿日:2020-08-17T07:30:22+09:00
実務未経験者が顧客管理アプリを作ってみた 〜環境構築編その2〜
実務未経験者が顧客管理アプリを作ってみた 〜環境構築編その1〜
前回は仮想マシンにDockerをインストールするところまで進めた。(一ヶ月前)
今回はDockerComposeをインストールして実際にコンテナを立ち上げていく。
ちなみにDockerComposeとはymlファイルへの記述を基に、複数のコンテナの定義、実行ができる非常に便利なツールである。手順
1.DockerComposeをインストールする
2.docker-compose.ymlを作成する
3.Dockerfileを作成する
4.コンテナを起動する1.DockerComposeをインストールする
仮想マシン内で以下コマンドを実行する。
DockerCompose公式を参考にする。
https://docs.docker.jp/compose/install.html#linux#インストール $sudo curl -L https://github.com/docker/compose/releases/download/1.16.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose #実行権限の付与 $sudo chmod +x /usr/local/bin/docker-compose #確認 docker-compose --version2.docker-compose.ymlを作成する
一度仮想マシンから出て、docker-compose.ymlを作成する。
qiita-dockerディレクトリ内に作ろう。%cd qiita-docker %touch docker-compose.ymlDockerfileを基にWebコンテナを構築すること、
qiita-dockerディレクトリ内に後ほど作成するLaravelアプリとWebコンテナの/var/www/htmlのデータを共有すること等を記述する。docker-compose.ymlversion: '3' services: web: build: ./ volumes: - ./qiita-docker/app:/var/www/html ports: - "8080:80" depends_on: - db db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: qiita MYSQL_USER: root MYSQL_PASSWORD: root3.Dockerfileを作成する
続いてqiita-dockerディレクトリ内にDockerfileを作成していく。
ベースとなるDockerイメージを指定、拡張機能の追加など記述する。
今回はphp:7.2-apacheにエディタであるVim、MySQLとの連携に必要なpdo_mysql、Laravelアプリを作成するのに必要なComposer、zip、unzip等をインストール。FROM php:7.2-apache COPY --from=composer:latest /usr/bin/composer /usr/bin/composer RUN apt-get update RUN apt-get install -y vim RUN docker-php-ext-install pdo_mysql RUN apt-get install -y git zip unzip4.コンテナを起動する
さっそく仮想マシン内のqiita-docker直下でコンテナを立ち上げる。
$docker-compose up -d ERROR: Couldn't connect to Docker daemon at http+docker://localunixsocket - is it running?エラーが出ます。
Docker daemonにコネクトできない、と。
Dockerの使用権限はデフォルトではrootのみに与えられているようです。
sudoコマンドを使うもしくは、dockerグループにユーザーを追加しなければいけない。
今回はdockerグループにユーザーを追加する。#dockerグループが存在しているか確認 $cat /etc/group | grep docker #存在していなければ作成 $sudo groupadd docker #現在ログイン中のユーザーをdockerグループに追加する $sudo gpasswd -a $USER docker反映させるために一度exitして仮想マシンに入り直す。
もう一度コンテナを立ち上げる。$docker-compose up -d指定したIPアドレスのポート番号にアクセスしてみよう。
こちらの画面が表示されれば成功です!次回はLaravelをインストールして表示させるところまで進めます。