- 投稿日:2020-05-21T22:37:04+09:00
PHP Deprecated: PHP Startup: Use of mbstring.internal_encoding is deprecated
PHP の開発バージョン(
master
/8.0.0-dev
)で、mbstring
を有効にするとmbstring.internal_encoding is deprecated
と表示される。$ docker run --rm -it keinos/php8-jit /bin/sh / $ php -v PHP Deprecated: PHP Startup: Use of mbstring.internal_encoding is deprecated in Unknown on line 0 Deprecated: PHP Startup: Use of mbstring.internal_encoding is deprecated in Unknown on line 0 PHP 8.0.0-dev (cli) (built: May 21 2020 15:58:54) ( NTS ) Copyright (c) The PHP Group Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies with Zend OPcache v8.0.0-dev, Copyright (c), by Zend TechnologiesTL; DR
mbstring.internal_encoding
は PHP 8.0.0 から削除されます。予定
mbstring.internal_encoding
を設定しているphp.ini
ファイルを探し、mbstring.internal_encoding
の項目を削除(or;
でコメントアウト)する。/usr/local/etc/php/conf.d/docker-php-enable-mb.ini; Extended PHP.ini file to enable mbstring. ; ========================================= ; Place this file under /usr/local/etc/php/conf.d/ zend.multibyte = On zend.script_encoding = UTF-8 mbstring.language = Japanese - mbstring.internal_encoding = UTF-8 + ;mbstring.internal_encoding = UTF-8TS; DR
もともと
php.ini
のmbstring.internal_encoding
設定は PHP 5.6.0 以降から非推奨でした。
master
ブランチ(PHP 8.0.0-dev)のコミット 3ca08ee(2020/03/31)で削除されDeprecated
となり、このまま差し戻されなければ PHP 8.0.0 の正式リリースで削除されます。おそらく、
mbstring.internal_encoding
以外の非推奨設定も PHP 8.0.0 を機に削除されるかもしれません。2020/05/21 現在、非推奨とされる
mbstring
の設定項目。
名前 変更履歴 変更予定 mbstring.http_input PHP 5.6.0 で非推奨 mbstring.http_output PHP 5.6.0 で非推奨 mbstring.internal_encoding PHP 5.6.0 で非推奨 PHP 8.0.0 で削除予定(3ca08eeで削除) mbstring.func_overload PHP 7.2.0 で非推奨
- 投稿日:2020-05-21T20:51:47+09:00
EC-CUBE4 で割引明細を追加するカスタマイズの例
EC-CUBE4 で割引明細を追加するカスタマイズの例
以下のPHPファイルを作成する。
app/Customize/Service/PurchaseFlow/Processor/TestDiscountProcessor.php
app/Customize/Service/PurchaseFlow/Processor/TestDiscountProcessor.php<?php namespace Customize\Service\PurchaseFlow\Processor; use Doctrine\ORM\EntityManagerInterface; use Eccube\Annotation\OrderFlow; use Eccube\Annotation\ShoppingFlow; use Eccube\Entity\ItemHolderInterface; use Eccube\Entity\Master\OrderItemType; use Eccube\Entity\Master\TaxDisplayType; use Eccube\Entity\Master\TaxType; use Eccube\Entity\OrderItem; use Eccube\Service\PurchaseFlow\PurchaseContext; use Eccube\Service\PurchaseFlow\DiscountProcessor; /** * 値引きを追加する. * * ここでフローを指定 * @ShoppingFlow * @OrderFlow */ class TestDiscountProcessor implements DiscountProcessor { /** * @var EntityManagerInterface */ protected $entityManager; /** * TestDiscountProcessor constructor. * * @param EntityManagerInterface $entityManager */ public function __construct(EntityManagerInterface $entityManager) { $this->entityManager = $entityManager; } public function removeDiscountItem(ItemHolderInterface $itemHolder, PurchaseContext $context) { foreach ($itemHolder->getItems() as $item) { if ($item->getProcessorName() == TestDiscountProcessor::class) { $itemHolder->removeOrderItem($item); $this->entityManager->remove($item); } } } public function addDiscountItem(ItemHolderInterface $itemHolder, PurchaseContext $context) { /* 必要であればここで適応条件の制御をする * if... */ // 課税/不課税や税込/税抜はここで作成 $DiscountType = $this->entityManager->find(OrderItemType::class, OrderItemType::DISCOUNT); $TaxInclude = $this->entityManager->find(TaxDisplayType::class, TaxDisplayType::EXCLUDED); $Taxation = $this->entityManager->find(TaxType::class, TaxType::TAXATION); $OrderItem = new OrderItem(); $OrderItem->setProductName($DiscountType->getName()) ->setPrice(-100) ->setQuantity(1) ->setTax(-10) ->setTaxRate(10) ->setRoundingType(null) ->setOrderItemType($DiscountType) ->setTaxDisplayType($TaxInclude) ->setTaxType($Taxation) ->setOrder($itemHolder) ->setProcessorName(TestDiscountProcessor::class); $itemHolder->addItem($OrderItem); } }
- 投稿日:2020-05-21T20:51:02+09:00
WordPressでカスタマイズAPIを作る
WordPressの記事コンテンツをAPIとして書き出すには公式同等のWP REST APIが存在します。
しかし、セキュリティ面での懸念もあり、いろいろいらない情報が書き出されてしまったり、APIの構造が複雑になったり、いろいろデメリットがあります。
固定ページの機能を活用し、スラッグAPIの固定ページを作成し、テンプレートファイルにpage-api.phpファイルを作ってしまえば、カスタマイズしたAPIを簡単に作れちゃいます。
そのコードをご紹介します。page-api.php<?php // ベーシック認証 switch (true) { case !isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']): //ベーシック認証のID・PWを設定 case $_SERVER['PHP_AUTH_USER'] !== 'user_name': case $_SERVER['PHP_AUTH_PW'] !== 'pass_word': header('WWW-Authenticate: Basic realm="Enter username and password."', true, 401); header('Content-Type: text/plain; charset=utf-8'); die('認証が必要です'); } header('content-type: application/json; charset=utf-8'); header("Access-Control-Allow-Origin: *"); if($_SERVER["REQUEST_METHOD"] == "GET"){ $api_data = '['; if(isset($_GET['cat_api'])) {//カテゴリ指定がある場合 $cat_id = $_GET['cat_api']; $posts = wp_get_recent_posts(array( 'numberposts' => 10, 'post_status' => 'publish', 'category' => $cat_id ) );//公開済みの記事を対象に }else{//カテゴリ指定がない場合、全記事を対象に $posts = wp_get_recent_posts(array( 'numberposts' => 10, 'post_status' => 'publish,private' ) ); } foreach($posts as $post){ $post_id = $post["ID"]; $category = get_the_category($post_id); $cat_name = $category[0]->cat_name; $thumbnail = get_the_post_thumbnail_url($post_id); $update = get_the_modified_date("Y-m-d H:i:s", $post_id); $status = get_post_status($post_id); $post_url = '{"url":' . site_url() . $post["post_name"] . '/",'; $post_image = '"image":' . '"' . $thumbnail .'",'; $post_date = '"date":' . '"' . $post["post_date"] . '",'; $post_update = '"update":' . '"' . $update . '",'; $post_status = '"status":' . '"' . $status . '",'; $post_category = '"category":' . '"' . $cat_name . '",'; $post_title = '"title":' . '"' . $post["post_title"] . '",'; $post_excerpt = '"expert":' . '"' . $post["post_excerpt"] . '",'; $post_content = $post["post_content"]; $post_content = wpautop($post_content); //余計な改行等々 // 関連記事のデータ生成 $relateds = wp_get_recent_posts(array( 'numberposts' => 3, 'post_status' => 'publish', 'category' => $category_id ) ); $post_related = '<h2>関連記事</h2><ul>'; foreach($relateds as $related){ $related_id = $related["ID"]; $post_related .= '<li><a href="' . site_url() . $related["post_name"] . '/">' . $related["post_title"] . '</li>'; } $post_related .= '</ul>'; $post_content .= $post_related; $post_content = str_replace('"', '\"', $post_content); $post_content = '"content":' . '"' . $post_content . '"},'; $api_data = $api_data . $post_url . $post_image . $post_date . $post_update . $post_status . $post_category . $post_title . $post_excerpt . $post_content; } $api_data = $api_data . ']'; $api_data = preg_replace('/(?:\n|\r|\r\n)/', '', $api_data ); //余計なものを一括置換 $api_data = str_replace('},]', '}]', $api_data); $api_data = str_replace(' ', '', $api_data); print $api_data; }else{ header("HTTP/1.0 404 Not Found"); return; }
- 投稿日:2020-05-21T20:33:16+09:00
WordPressのAMP対応をLazyLoadと両立
WordPressの記事コンテンツをAMP対応するために、いろいろプラグインがありますが、
無料のものだと、なかなかうまくいかず、予期せぬバグが発生したり、デザイン面のカスタマイズができなかったり、
いろいろと厳しい部分があります。
プラグインを使わずに、テンプレートファイルをカスタマイズすれば、意外にも簡単に対応できます。
こちらの記事で詳しく紹介されて、参考させていただきました。
WordPressをプラグインなしでAMP対応させる試み【その1:格闘編】しかし、Lazyloadを使う場合、画像の一括置換でバッティングしてしまい、うまく動作しません。
そのために、functions.phpの該当箇所を両立できるように対応してみました。
ご参考まで!functions.phpfunction add_image_placeholders( $content ) { if(isset($_GET['amp']) && is_single()){//ampの場合はampタグに置換 $content = preg_replace( '#<img([^>]+?)src=[\'"]?([^\'"\s>]+)[\'"]?([^>]*)>#', sprintf( '<amp-img layout="responsive" src="${2}"${3}></amp-img>' ), $content ); return $content; }elseif( is_feed() || is_preview() || ( function_exists( 'is_mobile' ) && is_mobile() ) ){//プレビューやフィードモバイルなどで遅延させない return $content; }elseif ( false !== strpos( $content, 'data-original' ) ){//既に適用させているところは処理しない return $content; }else{$content = preg_replace(//Lazyload画像正規表現で置換 '#<img([^>]+?)src=[\'"]?([^\'"\s>]+)[\'"]?([^>]*)>#',//IMGタグの正規表現 sprintf( '<img${1}src="%s" data-lazy="true" data-original="${2}"${3}><noscript><img${1}src="${2}"${3}></noscript>', get_template_directory_uri().'/images/lazy_image.gif' ),//置換するIMGタグ(JavaScriptがオフのとき用のnoscriptタグも追加) $content);//投稿本文(置換する文章) return $content; } } add_filter( 'the_content', 'add_image_placeholders', 99 ); add_filter( 'post_thumbnail_html', 'add_image_placeholders', 11 );
- 投稿日:2020-05-21T19:51:27+09:00
Dockerで構築したLaravel環境に、PHPStormでステップ実行デバッグを仕掛ける
元記事 Windows10 に PHP7+Laravel環境をDockerで構築
← 元元記事 最強のLaravel開発環境をDockerを使って構築する【新編集版】目的
Dockerで構築したLaravel環境で実行されているPHPコードに対して、
JetBrains社の「PHPStorm」からステップ実行デバッグができるようにしたい。ステップ実行デバッグとは
- コードの好きな行で実行を停止させて、その瞬間の変数リストや変数の中身を覗き見する。
- 1行ずつ実行させて、どこでエラー落ちするか追跡する。
といったことができる、例のアレ↓です。
概要
今回、PHP定番デバッグツール xdebug を使用して実現していきます。
(Laravel最新版には他にもイケてるデバッグツールがあると思いますが、諸事情で古いバージョンのLaravelを使用しなければならなかったので)PHPはサーバーサイドプログラミング言語なので、サーバー側に xdebug をインストールしてデバッグ情報を吐き出させるようにしないといけません。
加えて今回はWindowsやMacに構築したDockerコンテナの中でサーバーを動かしているので、ちょっとだけ工夫が必要です。
環境
- 冒頭に書いた元記事および元元記事の手順でLaravel環境を構築済であること。
- PHPStorm最新版を使って開発している方。(Windows版 / Mac版 両方で動作確認済。)
- 開発環境や検証環境。(本番環境ではデバッグツールを入れる必要は無いので。)
方法
サーバー側作業
PHPサーバーに xdebug インストール&有効化する設定を追記する。
PHPサーバーは今回 Docker で構築されるようになっているので、その設定ファイルをいじります。
2行ほど追記します。docker-laravel\infrastructure\docker\php\Dockerfile・ ・ ・ 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 && \ mkdir /var/run/php-fpm && \ docker-php-ext-install intl pdo_mysql mbstring zip bcmath && \ composer config -g process-timeout 3600 && \ composer config -g repos.packagist composer https://packagist.org && \ # ↓↓↓ 追記 ↓↓↓ pecl install xdebug && \ docker-php-ext-enable xdebug && \ # ↑↑↑ 追記 ↑↑↑ composer global require hirak/prestissimo ・ ・ ・PHPサーバーの php.ini に xdebug 設定を追記する。
php.ini には「
host.docker.internal
というマシンからのデバッグ通信要求を受け付けてね」という設定を追加します。「
host.docker.internal
って、そんなアドレスのマシン持ってないぞ…?」
と思うかもですが、ご安心ください。
最近の Docker Desktop をお使いであれば、自動でそういう設定をしてくれます。これはDockerホスト、つまりお手元の Windows や Mac を指し示すようになっています。
(Dockerコンテナの中からlocalhost
とやるとDockerコンテナ自身を指してしまいDockerホストを辿れないので、こういう機能を用意してくれています。)docker-laravel\infrastructure\docker\php\php.ini・ ・ ・ [Assertion] zend.assertions = 1 [mbstring] mbstring.language = Japanese # ↓↓↓ 追記 ↓↓↓ [xdebug] xdebug.idekey="PHPStorm" xdebug.remote_host = "host.docker.internal" xdebug.default_enable = 1 xdebug.remote_autostart = 1 xdebug.remote_connect_back = 0 xdebug.remote_enable = 1 xdebug.remote_handler = "dbgp" xdebug.remote_port = 9000 # ↑↑↑ 追記 ↑↑↑Laravel環境を起動しておく。
これは元記事のまま、コマンド実行するだけです。
初めて実行する場合cd docker-laravel/infrastructure make create-project
すでにcreate-projectを実行したことがある場合cd docker-laravel/infrastructure make remake
コマンドが完了するまでお待ちください。
数分かかるかもです。クライアント側作業(お手元のWindows/Mac)
PHPStormを起動する。
ここで初めて起動してください。
もしすでに起動してdocker-laravelプロジェクトを開いてしまっていたら、再起動してください。
(Dockerビルド工程でインポートされたPHPライブラリ群がインクルードされていない場合があるので。)PHPStormで docker-laravel プロジェクトを開く。
メインメニューから設定画面を開く。
左メニューから
Languages & Frameworks
→PHP
を開く。画面右側の
+
を押す。(ついでに、
Include Path
欄にPHPライブラリ群がずらーっとインクルードされているか念のため確認してください。)docker-laravel を選択して
OK
を押す。今回のプロジェクトのルートディレクトリを丸ごとパスに追加します。
左メニューから
Languages & Frameworks
→PHP
→Debug
を開き、xdebug
→Debug port:
9000 にする。最初から 9000 だったらそのままでOKです。
次へ進みましょう。余談ですがなぜ 9000 かというと、今回PHPコードを処理するのはポート9000で待ち受けている php-fpm というソフトだからです。WEBアクセスそのものはポート80で待ち受けているWEBサーバーソフト nginx が処理しますが、PHP処理だけ php-fpm に投げる、という動きになります。
(ちょっと前のモダン構成だと、Apache というWEBサーバソフトがポート80で待ち受け、WEBもPHPも一括処理するというのが多かったです。)左メニューから
Languages & Frameworks
→PHP
→Servers
を開き+
を押す。以下の設定をして、最後に
Apply
ボタンを押す。
設定項目 値 Name docker-laravel Host localhost Port 9000 Debugger Xdebug User path mappings チェックON 終わったら閉じてOKです。
メインウィンドウ右上の
Add Configuration...
を押す。左ツリー上部の
+
を押してPHP Remote Debug
を選ぶ。以下の設定をして、最後に
Apply
ボタンを押す。
設定項目 値 Name docker-laravel Filter debug connection by IDE key チェックON Server docker-laravel IDE key PHPStorm 終わったら閉じてOKです。
メインウィンドウ右上の
緑色の昆虫
ボタンを押す。そうすると、
緑色の昆虫
ボタンの右のほうに赤い■
ボタンが点灯します。- メインウィンドウ下部にデバッグ情報小窓が展開されます。
これで、デバッグ中の状態になりました。
デバッグをやめるには赤い■
ボタンを押せばOKです。ブラウザでPHPサーバーにアクセスしてみる。
普通なら Laravel のサンプルページが開くはずですが、ずっとアクセス中のように見えます。
実は、デバッガーが割り込んで処理を途中で止めているのです!
デバッグ情報小窓→
Debugger
タブを開く。お、デバッグ情報が来てますね!
ただ、何やらエラーが表示されています。
「サーバーコードと手元のソースファイルが関連付けられていないから追跡できないよ!」と怒られています。なので、設定してあげましょう。
すぐ下の行に表示されているClick to set up path mappings
を押して下さい。以下の設定をして
OK
を押す。
- この画面、出現当初はすごくウィンドウが小さいので、広げてあげてください。
Use path mappings
にチェックを入れます。- 下部にプロジェクトファイルリストが出てくるので、
File path on server:
に示されているコードに相当するソースファイルを指定してあげます。再度PHPサーバーにアクセスしてみる。
あれ、今度はすんなりWEBページが表示されて終わってしまいました。
でも大丈夫、正常です!ソースコードの該当行にブレークポイントを仕掛けてみる。
このページのソースコードは
index.php
ですね。
ではindex.php
を開いて、停止させてみたい行の冒頭をクリックしてみてください。
赤い●
が点灯します。再々度PHPサーバーにアクセスしてみる。
来ました!
54行目で処理が一時停止され、その瞬間の変数などが丸見えです!といったことも可能です。
それでは長くなってしまいましたが、今回はここまで。
お疲れ様でした
- 投稿日:2020-05-21T19:11:02+09:00
簡易的なKrunkerのチートシート(?)を作りました
サイト
https://katudon.000webhostapp.com/Home.php
途中まで攻略サイトみたいにしようと思ったんですが、そんなサイトは既に山ほど存在してるので、ダメージとかを計算できるデータベース的なサイトにしました。なので、銃一覧以外は機能しません。
いつかherokuに移すかも。使用方法
見たい銃を銃一覧から選択するとその銃の性能を見ることができます。
ドロップダウンメニュー(画像の"スナイパーライフル"って書いてるやつ)を選ぶことでその場で他の銃と比較が出来ます。左上の+を押すことで複数の銃と比較も可能。-を押すと一番右端のやつが消えます。
威力のドロップダウンメニューから頭、胴、足それぞれのダメージを見ることが出来ます。
総合DPSは勝手に自分が勝手に編み出したDPSで、下に小さく書いてある通り、人間の反応速度やエイム速度等を反映したDPSを計算します。
総火力の所に数値を入れると(単位はms)、その間に与えることが出来るダメージの総数を計算します。
以上です。
- 投稿日:2020-05-21T15:31:50+09:00
ID重複例(雇用調整助成金の記事を見て)
はじめに
https://www3.nhk.or.jp/news/html/20200520/k10012437361000.html
雇用調整助成金オンライン申請に関する記事を見て、
じゃあ どんなときにIDが重複するのかを、私の経験を元にまとめました。最大値+1
[果物マスタ]
ID 果物 1 りんご 2 みかん 3 いちご : : 4 バナナ ( ← 同時に登録 ) 4 ぶどう ( ← 同時に登録 ) この場合、最大値3+1、すなわち「4」が次のIDです。
しかし複数のプロセスが「同時」に最大値を取得すると、
両プロセスともに「4」という重複IDが割り振られてしまいます。
(一方が4、他方が5とはならない。)コーディングにもよりますが、重複IDだけ割り振られて、
ぶどうの insert エラーをシステムが適切に処理しなかった場合、
ぶどうを登録したユーザに対して
「4:バナナ が正常に登録されました」
と、身に覚えのないメッセージが表示されるでしょう。※「最大値+1」が悪いわけではなくて、
設計やコーディングが甘いことが問題です。ランダム値
[果物マスタ]
ID 果物 13245 りんご 97860 みかん 82073 いちご : : 56718 バナナ ( ← ランダム値が重複 ) 56718 ぶどう ( ← ランダム値が重複 ) random_int() などで得られるランダム値をIDにする設計も、
たまに見かけます。これも重複IDが発生し得ます。マスタを同時刻に登録しようとして、偶然、同じランダム値が
生成される可能性は、ゼロではありません。IDの頭に「年月日時分秒」を付けても同じです。
同時刻に同じランダム値が生成されれば、意味がありません。ID使いまわし + 未削除データ
前述の「最大値+1」の場合ですが、
「4:バナナ」を削除したら、次回 登録時、
再度「4」が割り振られてしまいます。[果物マスタ(親)]
ID 果物 1 りんご 2 みかん 3 いちご 4 ぶどう ( ←「4:バナナ」を削除後 登録 ) [生産地マスタ(子)]
ID 果物 1 青森 2 和歌山 3 栃木 4 フィリピン ( ← 未削除データのため「山梨」を登録できない!) このとき共通IDの別テーブルに、
例えば果物マスタの子テーブル「生産地マスタ」に、
バナナの生産地「4:フィリピン」が うっかり消されずに残っていたら、
ぶどうの生産地が登録できないかもしれません。まとめ?
主キー、排他制御、トランザクション、AUTO_INCREMENT、シーケンス等を
システムに合わせて設計して、重複しないID生成を心がけましょう。
- 投稿日:2020-05-21T14:33:05+09:00
PHP �foreachで配列の最初と最後に処理を追加する
環境
- PHP 7.3 以上
コード
$items = [ 'tokyo' => '東京', 'shibuya' => '渋谷', 'harajuku' => '原宿', 'yoyogi' => '代々木', ]; $keyFirst = array_key_first($items); $keyLast = array_key_last($items); foreach ($items as $key => $value) { if ($key === $keyFirst) { echo "$value is first" . PHP_EOL; } if ($key === $keyLast) { echo "$value is last" . PHP_EOL; } }実行結果
tokyo: 東京 is first yoyogi: 代々木 is last補足: 配列から最初と最後の値だけ取り出したい場合
echo "{$items[array_key_first($items)]} is first" . PHP_EOL; echo "{$items[array_key_last($items)]} is last" . PHP_EOL;参考
- 投稿日:2020-05-21T13:10:26+09:00
サーバから日本語を含むファイル名が正しく取得できない
先頭の日本語だけ消える?
sample.php// (1)「file」というディレクトリ内のファイル一覧を配列形式で取得(「./file/ファイル名.jpg」というパス情報が取れる) // 拡張子を指定する時はglob("./file/*.jpg")のようにする $fileArray = glob("./file/*"); // (2)(1)で取得したファイル名の配列を1つずつ処理する foreach($fileArray as $filepath) { // basename()でファイル名のみ取得する。 $file_name = basename($filepath); }これでファイル名を取得できるはずなのですが、
ファイル名が全て日本語文字の場合は全て消えて、一部日本語文字の場合は先頭の日本語文字が消えてしまいます。
たとえば
「ファイルsample名前.pdf」→「sample名前.pdf」
「ファイル名.pdf」→「.pdf」
というようになってしまう。
(ちなみにPHPのバージョンは7.1(CGI版)/サーバはheteml)原因はロケールの設定に依存するため
php.iniでmbstring関連の設定をいじってみたのですが全くの無意味でした。
そして見つけたのが下記の記事
fgetcsv関数を文字化け対応 setlocaleの文字コード指定fgetcsv関数に関しての記事でしたが、症状は同じ…
ということでさっきのプログラムの頭にロケールの設定を足してみました。
(さっきのコメントは邪魔なので消しました)sample.php// ロケールの設定 setlocale(LC_ALL, 'ja_JP.UTF-8'); $fileArray = glob("./file/*"); foreach($fileArray as $filepath) { $file_name = basename($filepath); }結果は…
わーい!取得できた!よかった!
- 投稿日:2020-05-21T11:27:09+09:00
Laravel 7 ビューファイルで指定したアドレスにメールを送信する
目的
- ビューファイルからのトリガーでメールを送信する処理を追加する際、非常に詰まったため実施方法をまとめる
実施環境
- ハードウェア環境(下記の二つの環境で確認)
項目 情報 OS macOS Catalina(10.15.3) ハードウェア MacBook Pro (16-inch ,2019) プロセッサ 2.6 GHz 6コアIntel Core i7 メモリ 16 GB 2667 MHz DDR4 グラフィックス AMD Radeon Pro 5300M 4 GB Intel UHD Graphics 630 1536 MB
- ソフトウェア環境
項目 情報 備考 PHP バージョン 7.4.3 Homwbrewを用いて導入 Laravel バージョン 7.0.8 commposerを用いて導入 MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いて導入 前提情報
- ローカル開発環境を用いたメール送信処理の追加方法をまとめる。
- メール送信サーバはGmailを使用する。
- 下記の方法を実施して当該アプリからテストメールを送信できる状態にしていること。
概要
- 入力フォームの作成
- ルーティングファイルの編集
- コントローラファイルの作成と編集
- ビューフィアルの作成と編集
- メール送信の準備
- クラス作成
- 送信内容の作成
- 確認
詳細
入力フォームの作成
ルーティングファイルの編集
Laravelアプリ名ディレクトリで下記コマンドを実行してルーティングファイルを作成する。
$ vi routes/web.php開いたルーティングファイルを下記のルーティング情報を追記する。
アプリ名ディレクトリ/routes/web.phpRoute::get('/input', 'TestMailController@input'); Route::post('/send', 'TestMailController@send');保存して閉じる。
コントローラファイルの作成と編集
アプリ名ディレクトリで下記コマンドを実行してメール送信用のコントローラを作成する。
$ php artisan make:controller TestMailControllerアプリ名ディレクトリで下記コマンドを実行して先に作成したコントローラファイルを開く。
$ vi app/Http/Controllers/TestMailController.php開いたコントローラファイルを下記の様に修正する。
アプリ名ディレクトリ/app/Http/Controllers/TestMailController.php<?php namespace App\Http\Controllers; use Illuminate\Http\Request; //下記を追記する use Illuminate\Support\Facades\Mail; use App\Mail\TestSendMail; //上記までを追記する class TestMailController extends Controller { //下記を追記する public function input() { return view('test_mails.input'); } public function send(Request $request) { $contact = $request->all(); Mail::to($contact['email'])->send(new TestSnedMail()); return redirect('/input'); } //上記までを追記する }保存して閉じる
ビューフィアルの作成と編集
アプリ名ディレクトリで下記コマンドを実行してビューファイルを格納するディレクトリを作成する。
$ mkdir resources/views/test_mailsアプリ名ディレクトリで下記コマンドを実行してビューファイルを開く。
$ vi resources/views/test_mails/input.blade.php開いたビューファイルを下記の様に修正する。
アプリ名ディレクトリ/resources/views/test_mails/input.blade.php<form action="/send" method="POST"> @csrf <input type="text" name="email"> <input type="submit"> </form>確認
アプリ名ディレクトリで下記コマンドを実行してローカルサーバを起動する。
$ php artisan serve下記にアクセスする。
下記の様に入力欄と送信ボタンが表示されている事を確認する。
メール送信の準備
アプリ名ディレクトリで下記コマンドを実行してメール送信専用のクラスが記載されたファイルを作成する。
$ php artisan make:mail TestSendMailアプリ名ディレクトリで下記コマンドを実行して先に作成したメール送信専用のクラスファイルを開く
$ vi app/Mail/TestSendMail.php開いたクラスファイルを下記の様に修正する。
アプリ名ディレクトリ/app/Mail/TestSendMail.php<?php namespace App\Mail; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels; class TestSendMail 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 //メールの件名 ->subject('Test Mail') //メールとして表示したいビューファイル ->view('mails.test'); } }アプリ名ディレクトリで下記コマンドを実行してメールとして表示したいビューファイルを格納するディレクトリを作成する。
$ mkdir resources/views/mailsアプリ名で下記コマンドを実行してメールとして表示したいビューファイル作成する。
$ vi resources/views/mails/test.blade.php開いたメールとして表示したいビューファイルを下記の様に編集する。
アプリ名ディレクトリ/resources/views/mails/test.blade.phpこれはテストメールです確認
アプリ名ディレクトリで下記コマンドを実行してローカルサーバを起動する。
$ php artisan serve下記にアクセスする。
入力欄にテストメールを送信したいアドレスを入力する(正常に実装できているとメールが送信されるので注意!!!)
入力したアドレスのメール受信ボックスを確認する。
下記の様にメールが受信できていれば実装完了である。
Laravel 7 アプリからメールを送信するも含めて正常に実装できていれば下記の様なメールが送信されるはずである。
- 投稿日:2020-05-21T09:31:48+09:00
WEBデータベース Exmentにブラウザでテーブルを作って入力してみる
Exmentとは
オープンソースのWEBデータベース
公式サイト:https://exment.net/WEBデータベースとは
データベースをブラウザで見たり入力したりすることができ、テーブルの定義もブラウザでできる。ノンプログラミングでデータベースを開発・運用できることが特徴です。
レンタルサーバーなどに設置すれば、テレワークで重宝するかもしれません。やってみたこと
今回は公式のデモサイトを使って、実際にブラウザからデータベースのテーブルを作ってデータを入力するところまでを紹介します。
テーブル作成
①以下のURLを開く
基本的なデモ環境です。
管理者権限を付与しております。
URL: https://demo-jp.exment.net/admin②以下を入力して「ログイン」をクリック
ログインID : admin
パスワード: adminadmin③左側の「管理者設定」をクリック
④「カスタムテーブル」をクリック
⑤「新規」をクリック
⑥テーブル名を入れる
「テーブル名」に英数字でテーブル名(例 EMPLOYEE_LIST)
「テーブル表示名」に日本語でテーブル表示名(例 社員リスト)を入れる
⑦「権限設定」と「メニューに追加する」をONにする
⑧「送信」をクリック
⑨「カスタムテーブル」をクリックし、追加したテーブル「EMPLOYEE_LIST」をクリック
⑩「テーブル詳細設定」をクリック
⑪「カスタム列設定」をクリック
⑫「新規」をクリック
⑬「名前」を追加
「列名(英数字)」に「NAME」、「列表示名」に「名前」を入れ
「列種類」を「一行テキスト」にする。
「必須」を「YES」、「ユニーク」を「YES」にし、「送信」を押す
⑭「性別」を追加
「新規」をクリック。「列名(英数字)」に「GENDER」、「列表示名」に「性別」を入れ
「列種類」を「一行テキスト」にし「送信」を押す。
⑮「社員番号」を追加
「新規」をクリック。「列名(英数字)」に「EMPLOYEE_NO」、「列表示名」に「社員番号」を入れ
「列種類」を「整数」にし「送信」を押す。
⑯一度、「ログアウト」「ログイン」する(※ここではまった)
⑰左のメニューに「社員リスト」が追加されるので、クリックする。
⑱「新規」をクリック
⑲「名前」「性別」「社員番号」を入力し「送信」を押す
⑳データが保存される。データの行をクリックすると、データを確認できる。
- 投稿日:2020-05-21T06:29:43+09:00
Laravel7をGCP Google App EngineへデプロイしてDBマイグレーションしてプロモートする
やりたいこと
- デプロイとDBマイグレーションとプロモート(サイト公開)をコマンドで実行したい
ソース
composer.json
composer gcloud-deploy
コマンドを登録するchmod 744 gcloud-deploy.bash
で実行権限を付与しておく"scripts": { "gcloud-deploy": [ "./gcloud-deploy.bash" ],gcloud-deploy.bash
- jsonのパースにjqを使うのでインストールしておくこと
#!/bin/bash #set -eu # proxy実行しているかチェックする proxyCommand="cloud_sql_proxy -instances=foo-bar-apps:asia-northeast1:foo-bar-mysql=tcp:3306" ps auxww|grep "$proxyCommand"|grep -v grep if [ "$?" = "1" ];then echo "You must run proxy to do db migrate." echo "" echo $proxyCommand echo "" exit 1 fi npm run prod # DBマイグレーションはlocalで実行しproxy経由でproductionに接続されるので # 設定ファイルを.env.productionからコピーする # 現在使用中の.envはバックアップしておく rand=$RANDOM && cp .env .env.backup.$rand && cp .env.production .env message=`php artisan migrate --pretend` cp .env.backup.$rand .env # DBマイグレーションが必要かチェックする if [ "$message" = "Nothing to migrate." ]; then # DBマイグレーションが不要なら通常のデプロイ echo "Youd don't need to do db migrate." echo "Laravel artisan said: $message" gcloud app deploy --quiet --project=foo-bar --verbosity=info else # DBマイグレーションが必要ならデプロイオプションで--no-promote(公開しない) echo "Youd need to do db migrate." echo "Laravel artisan said: $message" gcloud app deploy --quiet --project=foo-bar --verbosity=info --no-promote --format=json | tee gcloud-version.json # DBマイグレーションする rand=$RANDOM && cp .env .env.backup.$rand && cp .env.production .env php artisan migrate --force cp .env.backup.$rand .env # --promote(公開する) appVersion=`jq -r '.versions[0].id' gcloud-version.json` gcloud app deploy --promote --quiet --version=$appVersion fi.gcloudignore
gcloud-version.json .env.backup.*ほか
- コマンドひとつで実行できるようにはなったが課題はある
.env.production
と.env.local
の管理したくない- というか、現在使用中の.envを上書きされたくない...
- マイグレーションの必要がないなら通常の
gcloud app deploy
を実行したい
migrate --pretend
を判定して分岐してみた- デバッグ用にdeploy.logとか出力するとsha mismatchエラーでbuildがコケた
.gcloudignore
に追加すると解決したような気がするStep #0 - "fetcher": Failed to fetch gs://staging.foo-bar-hoge.appspot.com/xxxx, will no longer retry: fetching "gs://staging.foo-bar-hoge.appspot.com/xxxx" with timeout 1h0m0s to temp file "/workspace/.download/xxxx": deploy.log SHA mismatch, got "xxxx", want "xxxx"参考
- 投稿日:2020-05-21T00:58:56+09:00
プチ・クラウドストレージ作ってみた
初めまして
60歳を間近にウェブデザイナーを目指して独学で勉強しているお婆ちゃんです。
去年の暮れからphpを勉強して、初めてシステムらしきものを作ってみました。
やりたいこと
プチ・クラウドストレージ
・ファイルをどこからでもアップ、保管してダウンロードもできる。
・セキュリティも兼ねてIDとパスワードでログイン形式にする。
まずはパワーポイントでサイトの系図を設計しました。
Excel、Word、パワポは商工会議所で習いたてホヤホヤです。
それぞれ基礎編までクリアして1月半くらいかかりました。
全部で5万円くらいはかかったかな。次は手順を考えてイメージを具体化。
これは無料版のAdobeのXDを使ってみました。操作も簡単で、感覚的に作れちゃうので便利です。
ページ自体はシンプルにしたかったのでフォントだけで作りました。
コードを書くのもAdobeの無料Brackets。Adobeドップリ。
ログインページ
AdobeXD イメージ図
パスワードとIDはここで決めてます。
空文字NGの条件も設定。login.php//XSS function html_escape($word){ return htmlspecialchars($word,ENT_QUOTES,'UTF-8'); } $logid = ''; $pass = ''; $messege = ''; if($_SERVER['REQUEST_METHOD'] === 'POST'){ //isset入れると空文字条件が効かない $logid = $_POST['logid']; $pass = $_POST['pass']; $logid = html_escape($logid); $pass = html_escape($pass); //IDとパスワード設定 if($logid === 'keserasera' && $pass === 'keserasera'){ session_start(); $_SESSION['login'] = 1; //ファイル一覧へリロード header('Location: file_list.php'); exit(); } elseif ($logid === '' || $pass === ''){ $messege = '<p class="notice"><i class="fas fa-info-circle"></i>IDとパスワードを空文字にしないで入力してください</p>'; } else { $messege = '<p class="notice"><i class="fas fa-info-circle"></i>IDかパスワード、もしくは両方違います</p>'; } } ?> <!doctype html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>ファイル預かり処・マイ保管庫</title> <!-- headerインクルード --> <?php require_once(dirname(__FILE__).'/header.php'); ?> <div id="wrapper"> <header> <div id="titleVar"> <h1><i class="fas fa-circle"></i>ファイル預かり処 マイ保管庫</h1> <p class="tx14">ログインページ</p> </div> <div id="topvew">My keep folder</div> </header> <main id="topMain"> <form action="" method="post" class="clearfix"> <label>ログインID</label> <input type="text" name="logid"> <label>パスワード</label> <input type="password" name="pass"> <p id="logBtn"><input type="submit" value="ログイン"></p> <?php echo $messege; ?> </form> </main> <!-- footerインクルード --> <?php require_once(dirname(__FILE__).'/footer.php'); ?>
ファイルリスト一覧ページ
アップロードとダウンロード、削除ファイルの3つのformがあります。
- アップロードは同一ページで処理
- ダウンロードはチェックページに飛ばしてリロード処理
- 削除ファイルは確認ページを別に作ってpostデータを渡す
ちなみにダウンロードと削除ファイル一覧リストはタブ切替。
フォルダーの中身は一緒でアップすると自動でリストが増えて、削除すると減っていきます。file_list.php//ログインしていないとアクセスさせない session_start(); session_regenerate_id(true); if(isset($_SESSION['login']) === false){ header('Location: un_login.php'); exit(); } function html_escape($word){ return htmlspecialchars($word,ENT_QUOTES,'UTF-8'); } $up_file = ''; $messege = ''; $select_file = '<p id="take">ファイルを選択して下さい</p>'; $restore = ''; $up_before = 'upBefore';//非表示css $filename = ''; if($_SERVER['REQUEST_METHOD'] === 'POST'){ $up_file = $_FILES['file_up']; $up_file['name'] = html_escape($up_file['name']); $up_file['name'] = strtolower($up_file['name']);//英小文字に変換 //var_dump($up_file['name']); $extension = Pathinfo($up_file['name'],PATHINFO_EXTENSION);//.以降の拡張子を重複しないよう整形 $filename = Pathinfo($up_file['name'],PATHINFO_FILENAME); $filename = str_replace('.', '', $filename);//ファイル名に.があったら除去 //ファイルがNGの場合条件処理 if($up_file['size'] > 30000000 ){ $messege = '<p id="caution" class="notice"><i class="fas fa-info-circle"></i>ファイルサイズが容量を超えています</p>'; $up_before = 'upBefore'; $restore = '<a id="restore" href="file_list.php">こちらからUPし直してください</a>'; $select_file = ''; //phpよりエラー表示を出すときファイル選択ボタンを出さないで再読み込みさせる為、空文字設定に } else { $messege = ''; $up_before = 'upAfter'; //fileをアップする関数 move_uploaded_file($up_file['tmp_name'], './up_file/'.$filename.'.'.$extension); } } //upフォルダの中身 $dw_items = glob('./up_file/*');//DL用、同じでも変数変えないとエラーになる $del_items = glob('./up_file/*');//削除用、grobは配列形式でファイルパスを取得 ?> <!doctype html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>ファイル一覧 | ファイル預かり処・マイ保管庫</title> <!-- headerインクルード --> <?php require_once(dirname(__FILE__).'/header.php'); ?> <div id="wrapper"> <header> <div id="titleVar"> <h1><i class="fas fa-circle"></i>ファイル預かり処 マイ保管庫</h1> <p id="logout"><a href="logout.php">ログアウト</a></p> </div> <div id="topvew">My keep folder</div> </header> <main id="main"> <h2 class="pageTitle">ファイルアップロード</h2> <!-- ファイルup --> <form id="upBox" action="" method="post" enctype="multipart/form-data"> <p class="notice">※ファイルsizeは1つにつき30MBまで、名前は英小文字で</p> <!-- UPし直し表示 --> <?php echo $restore; ?> <p id="<?php echo $up_before; ?>">ファイル「<?php echo $filename; ?>」はアップされました。続けてUPできます</p> <label for="up"> <!-- ファイルを選択ボタン --> <?php echo $select_file; ?> </label> <input id="up" type="file" name="file_up"> <p id="send"><input type="submit" value=""></p> <!-- エラーメッセージ --> <?php echo $messege; ?> </form> <div id="fileBox"> <p id="fileCount">ファイル数<?php echo count($dw_items); ?>項目</p> <h3 class="selectFile" id="dowTab">DL用ファイル一覧</h3> <form id="dowList" action="download_file.php" method="post"> <!-- 飛び先でLocation リダイレクト処理--> <ul> <?php if(count($dw_items) === 0): ?> <li>まだファイルはありません</li> <?php else: ?> <?php foreach($dw_items as $items): $dw_name = Pathinfo($items,PATHINFO_BASENAME); ?> <li><input type="radio" name="dowl" value="<?php echo $dw_name; ?>"><?php echo $dw_name; ?></li> <?php endforeach; ?> <?php endif; ?> </ul> <p class="notice">※左のラジオボタンにチェックしてダウンロードボタンをクリックしてください</p> <p class="pibtn"><input type="submit" value="Download"></p> </form> <h3 class="selectFile" id="delTab">削除用ファイル一覧</h3> <form id="delList" action="delete_confilm.php" method="post"> <ul> <?php if(count($dw_items) === 0): ?> <li>まだファイルはありません</li> <?php else: ?> <?php foreach($del_items as $items): $del_name = Pathinfo($items,PATHINFO_BASENAME); ?> <li><input type="checkbox" name="del[]" value="<?php echo $del_name; ?>"><?php echo $del_name; ?></li> <?php endforeach; ?> <?php endif; ?> </ul> <p class="notice">※左のチェックボックスを選択(複数可)して確認ボタンをクリックしてください</p> <p class="vibtn"><input type="submit" value="削除確認"></p> </form> </div><!-- //id="fileBox"--> </main> <!-- footerインクルード --> <?php require_once(dirname(__FILE__).'/footer.php'); ?>
input type="file"は特殊で、デフォルトのボタンを使うのはビジュアル面で抵抗があったのでカスタマイズしました。
ファイルが選択されたらボタンがファイル名に変わって、エラーだったら上にメッセージ表示。
OKだったらtype="file"ブロックは非表示になって、type="submit"にすり替え。
見た目は一緒のボタンです。
ファイルアップが成功したらボタンの上にファイル名が表示して下のリストに追加といった仕様。
ただ、ファイル選択時はsubmitボタンは押されてないのでphpでの処理が難しい。
そこでjsのchangeイベントを活用。
ここは頭がこんがらがりました。phpとjsとcssのトリプル連携。submit_btn.jsjQuery('#up').on('change',function(){ const upfile = jQuery('#up').get(0).files; console.log(upfile); //多次元配列になるのかな? upfile[[name: xxx,size: xxx]] const fileName = upfile[0].name; console.log(fileName); //file選択をsubmitにすり替え jQuery('#take').css('display','none'); jQuery('#send').css('display','block'); jQuery('#send input').val(fileName); });
ダウンロードチェックページ
ダウンロードのコードはどうしても分からなかったのでググって動いたものをコピペさせてもらいました。
それまではダウンロードできても開けられなかったり不具合続出。
ここのコードが一番難しい。
今の段階では理解できなかったのですが、そのうち自分でも組めるようになりたいです。download_file.php//ダウンロードチェック session_start(); session_regenerate_id(true); if(isset($_SESSION['login']) === false){ header('Location: un_login.php'); exit(); } function html_escape($word){ return htmlspecialchars($word,ENT_QUOTES,'UTF-8'); } $dowload_file = $_POST['dowl']; $dowload_file = html_escape($dowload_file); //var_dump($dowload_file); //ここのコード分からなかったのでネットからググって拾ってきた function download($pPath, $pMimeType = null){ //-- ファイルが読めない時はエラー(もっときちんと書いた方が良いが今回は割愛) if (!is_readable($pPath)) { die($pPath); } //-- Content-Typeとして送信するMIMEタイプ(第2引数を渡さない場合は自動判定) ※詳細は後述 $mimeType = (isset($pMimeType)) ? $pMimeType : (new finfo(FILEINFO_MIME_TYPE))->file($pPath); //-- 適切なMIMEタイプが得られない時は、未知のファイルを示すapplication/octet-streamとする if (!preg_match('/\A\S+?\/\S+/', $mimeType)) { $mimeType = 'application/octet-stream'; } //-- Content-Type header('Content-Type: ' . $mimeType); //-- ウェブブラウザが独自にMIMEタイプを判断する処理を抑止する header('X-Content-Type-Options: nosniff'); //-- ダウンロードファイルのサイズ header('Content-Length: ' . filesize($pPath)); //-- ダウンロード時のファイル名 header('Content-Disposition: attachment; filename="' . basename($pPath) . '"'); //-- keep-aliveを無効にする header('Connection: close'); //-- readfile()の前に出力バッファリングを無効化する ※詳細は後述 while (ob_get_level()) { ob_end_clean(); } //-- 出力 readfile($pPath); //-- 最後に終了させるのを忘れない exit; } //選択されたファイルがあったらダウンロード、なかったらそのままリダイレクト //選択されないままダウンロード処理されちゃうと変なファイルがDLされる if(isset($_POST['dowl'])){ download('./up_file/'.$dowload_file); header('Location: file_list.php'); } else { header('Location: file_list.php'); }
削除確認、完了ページ
削除だけは誤って消してしまって後悔しないように、確認してからの導線にしました。
完了ページのレイアウトもほぼ一緒です。delete_done.php//ログインしていないとアクセスさせない session_start(); session_regenerate_id(true); if(isset($_SESSION['login']) === false){ header('Location: un_login.php'); exit(); } function html_escape($word){ return htmlspecialchars($word,ENT_QUOTES,'UTF-8'); } $delete_file = ''; //POSTで渡されたファイルを削除 if(isset($_POST['check'])){ for($i = 0; $i < count($_POST['check']); $i++){ unlink('./up_file/'.html_escape($_POST['check'][$i])); //削除ファイルli書出し $delete_file .= '<li><i class="far fa-file"></i>'.html_escape($_POST['check'][$i]).'</li>'; } } ?> <!doctype html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>ファイル削除完了 | ファイル預かり処・マイ保管庫</title> <!-- headerインクルード --> <?php require_once(dirname(__FILE__).'/header.php'); ?> <div id="wrapper"> <header> <div id="titleVar"> <h1><i class="fas fa-circle"></i>ファイル預かり処 マイ保管庫</h1> <p id="logout"><a href="logout.php">ログアウト</a></p> </div> <div id="topvew">My keep folder</div> </header> <main id="confiBox"> <h2 class="pageTitle">ファイル削除完了</h2> <p class="confiText">以下のファイルを削除しました</p> <ul> <?php echo $delete_file; ?> </ul> <p id="toListpage"><a href="file_list.php">ファイル一覧ページへ</a></p> </main> <!-- footerインクルード --> <?php require_once(dirname(__FILE__).'/footer.php'); ?>
ログアウトページ
ログアウトページは、お約束のセッション破棄だけなのでコードは省きます。
レイアウトはログインページと同じにしました。
ひとまず完成
あとは実際のサーバに上げて動作確認。
動いた時は大感動。
あれ?
でもアップできないファイルがある。色々試して、どうやら日本語名のファイルはアップできないみたい。
XAMPP開発時では日本語のファイル名でも大丈夫だったんですけどね。
そういえば日本語とサーバの相性は良くないと昔から言われてました。とりあえず注意書きに日本語NGと追加して応急対処。
preg_matchで条件設定した方がいいのかなと思ったりしています。ひとまず、これで完成として使い込んでみようと思ってます。
制作期間は約1週間、畑仕事と家事の合間にコツコツと作業お婆ちゃんのweb制作奮闘記でした。