- 投稿日:2020-07-26T21:33:20+09:00
Vue + Vue Router + Vuex + Laravel のチュートリアル(@MasahiroHarada 様 作成)を進める最中に詰まった部分の原因と対策
はじめに
こちらの記事は、@MasahiroHarada 様が作成された以下の記事をのチュートリアルを、実際に自分が行ってみて、詰まった部分や勘違いしていた部分を、他のチュートリアルに挑戦される方向けに残したものです。
Laravel 6 & PHP 7.4 対応】Vue + Vue Router + Vuex + Laravel チュートリアル(全16回)を書きました。
本当に素晴らしい記事で、Laravel、Vue、spa設計の知見が深まりました。
この場を借りて、改めて感謝申し上げます。※記事の執筆にあたり、一応ご本人から確認はいただいておりますが、共著でもないため、間違いやご指摘、ご意見は全て私の方に直接いただきますよう、お願い申し上げます。
それでは、よろしくお願いします。
私の開発環境と前提
私が使用した開発環境です。
Vue + Vue Router + Vuex + Laravelで写真共有アプリを作ろう (3) SPA開発環境とVue Routerの記事を参考に、Dockerで作成しました。
Laravelは7.19.0、OSはMacです。
詳しいことは上記の記事にまとめてくださっていますが、前提条件としてご了承ください。
次章より、実際に詰まった部分と解決策を述べます。npm run watch で何も表示されない
最初に遭遇したエラーは、(3) SPA開発環境とVue Routerのフロントエンドのビルドの項目で、
npm run watch
を入力しても何も表示されなかったことです。これは、SPAの仕組み上当然のことで、バックエンドの(Laravelの)サーバーを立てていなかったことが原因でした。
ターミナル等でシェルをもう一つ用意し、バックエンドのサーバーを
php artisan serve --host 0.0.0.0 --port 8081
で立ててから、もう一つの方でnpm run watch
を行うと無事表示されました。Illuminate\Foundation\Auth\RegistersUsers.php がない
(4) 認証APIの部分です。
これは、私と同じLaravelの7系、もしくは6系を使っている方は遭遇するかと思います。対策の詳細はこちらの公式にも載っています。
https://readouble.com/laravel/7.x/ja/authentication.html具体的には、以下のコードをcomposerファイルのあるディレクトリで実行すると現れてくれるはずです。
# Laravel6系 composer require laravel/ui:^1.0 --dev # Laravel7系 composer require laravel/ui参考記事
更新! Laravel6/7「make:Auth」が無くなった 〜Laravel6/7でのLogin機能の実装方法〜MyMemoなぜか新規登録ができない
これが個人的には一番びっくりなエラーでした。コードはあっていて新規登録をしようとしても、
POST http://localhost:3000/api/register 405 (Method Not Allowed) message: "The POST method is not supported for this route. Supported methods: GET, HEAD."
というエラーが出ます。これの解決策は、なんとパスワードを8文字以上にすることでした。
Railsのdeviseというログイン機能を作ってくれるライブラリをよく使用していて、そちらは6文字だったので油断しておりました。参考記事
ユーザー認証のパスワード制限の変更コントローラーで処理がされない
チュートリアルを進めていき、理解している部分は基本的にコピペで行っていたのですが、それ通りに貼り付けているはずなのにコントローラーが反応してくれない場面がありました。
原因はDIのし忘れ(namespaceの書き忘れ)でした。
Laravelでは別ファイルから使いたい要素を持ってくるときに、
use App\Http\Requests\StoreComment;
と言った形で主にファイル冒頭に記載をするのですが、内容のコードだけを貼って、こちらをコピペすることを忘れていました。結果、参照ができずにエラーが出たわけです。しっかりと記事を読んで、コピーのし忘れがないか確認しましょう。
Failed to load resource: the server responded with a status of 405 (Method Not Allowed) が出る
これは、ルーティングに誤りがありました。今回のチュートリアルでは、api.phpとweb.php二つのルーティングがあります。apiを叩く処理を間違った方に書いており、このエラーが発生しました。
「ルーティングを書いている場所はあっているか?」チェックをしてみると解決するかもしれません。
エラーメッセージがインポートできない
(8) エラーハンドリングの章で記載ミス?と思われるものがあります。(2020/7/26現在)コメント欄で@MasahiroHarada 様が回答されていますが、一応記事がそのままだったのでこちらにも記載いたしました。
router.jsimport SystemError from './pages/errors/System.vue' // ここのままだとエラーになるので、 // import SystemError from './pages/errors/500.vue' // としてあげましょう /* 中略 */ const routes = [ /* 中略 */ { path: '/500', component: SystemError } ]写真投稿APIのテストが通らない
Dockerで作開発をしていると、 (9) 写真投稿APIのテストが通りません、原因は記事のコメント欄にもあったのですが、一応こちらにまとめます。
基本的には紹介されているこちらの記事通りにやっていただきたいのですが、私はDocker初学者だったので「再ビルドしてください」というのが、どうするのかわかりませんでした。
私の場合
docker build -t vuesplash_vuesplash_web .
で解決しました。(ピリオドも必要です)謝辞
以上が、自分がチュートリアル実行時に遭遇したエラーです。初歩的なミスもあり、そのような振り返り学習ができたという点でも、こちらのチュートリアルは素晴らしかったです…本当にありがとうございました。
- 投稿日:2020-07-26T21:24:01+09:00
【PHP】2次元配列をキーを指定して並べ替える関数
array_multisortより便利?
array_multisortがあまり理解できていないのが悪いですが、とりあえずユーザ定義関数を作った。
キーを指定して、そのキーの値を基準に並べ替えができるように。
課題は某サイトで与えられたもの。
<?php // 自分の得意な言語で // Let's チャレンジ!! [$x, $y] = explode(" ", trim(fgets(STDIN)) ); $k = trim(fgets(STDIN)); $num_data = trim(fgets(STDIN)); for($i = 0; $i < $num_data; $i++ ) { [$xi, $yi, $price] = explode(" ", trim(fgets(STDIN))); $datas[$i]['x'] = $xi; $datas[$i]['y'] = $yi; $datas[$i]['price'] = $price; $datas[$i]['distance'] = ( $x - $xi )**2 + ( $y - $yi )**2; } $sum = 0; $sorted = val_sort($datas, 'distance'); for( $i = 0; $i < $k; $i++ ) { $sum += (int)$sorted[$i]['price']; } $ave = $sum / $k; echo $ave .PHP_EOL; function val_sort($array, $key) { // Loop through and get the values of our specified key foreach($array as $k=>$v) { $b[] = $v[$key]; } asort($b); // キーと要素を維持しつつ昇順 foreach($b as $k=>$v) { $c[] = $array[$k]; // 展開される順番にキーを取り出して元の配列のキーに指定して代入し直す } return $c; // 代入し直した配列を返す } ?>勉強になったこと
配列の展開をするときにforeachを使えるのは知っていたけど、キーも一緒に取り出して、そのキーをうまく使うと便利になるものだなということ。
- 投稿日:2020-07-26T21:00:51+09:00
MacにLaravelインストールしたメモ
この度初めてMacを購入してLaravelの環境構築を行ったのでメモ。調べながらトライしてなんとか完了。が、2つハマったポイントがありました。
1. システム環境
・macOS Catalina10.15.6
すべてMacのターミナルで作業。
(ちなみにハマった理由の1つがOSのverとターミナルが関係。)2. 手順
Ⅰ.Composerインストール
以下を実行。curl -sS https://getcomposer.org/installer | phpつぎにcomposerをどの作業場所からでも呼べるように設定。
mv composer.phar /usr/local/bin/composerインストール後,以下のコマンドで
composer______ / ____/___ ____ ___ ____ ____ ________ _____ / / / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/ / /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ / \____/\____/_/ /_/ /_/ .___/\____/____/\___/_/ /_/ Composer version 1.10.9 2020-07-16 12:57:00上記表示されれば正常にインストール完了。
Ⅱ.Composerを使ってLaravelインストーラーをインストール
以下を実行。
composer global require "laravel/installer"※ハマりポイント1
しかし、ここでエラー出てしまいました。どうもmacにはじめからインストールされているPHPではext-zipに対応してませんよ、ということらしい...(Problem1の2段目の部分)Changed current directory to /Users/ryo3110/.composer Using version ^3.2 for laravel/installer ./composer.json has been created Loading composer repositories with package information Updating dependencies (including require-dev) Your requirements could not be resolved to an installable set of packages. Problem 1 - Installation request for laravel/installer ^3.2 -> satisfiable by laravel/installer[v3.2.0]. - laravel/installer v3.2.0 requires ext-zip * -> the requested PHP extension zip is missing from your system. Installation failed, deleting ./composer.json.これをPHPをインストールし直すことで対応。MacにはHomebrewというパッケージ管理システムがあるのでこちらを利用する。
以下のコマンドでHomebrewをインストール
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"インストールできたらbrewコマンドでphpをインストールする。普段を使用しているので7.4にした。ここは使い慣れているver.でいいはず。
$ brew install php@7.4インストールできたら改めてLaravelインストーラーのインストールを試みる。
composer global require "laravel/installer"無事インストーラーのインストール完了。
Ⅲ.Laravelコマンド使えるようにパスを通す
この時点ではターミナル開いてすぐにLaravelコマンドが使えないのでパスの設定を行う。以下を実行。
echo "export PATH=~/.composer/vendor/bin:$PATH" >> ~/.bash_profile.bash_profileを読み込み。
source ~/.bash_profileこれでターミナルにlaravelと打てばlaravelコマンドが使える。
※ハマりポイント2
しかしターミナルをと閉じると使えなくなってしまう...どうもパスの設定が保存されていないのではと考えひたすら調べる。。。https://www.infoq.com/jp/news/2019/07/macos-ditches-bash-for-zsh/
どうやらCatalinaから標準シェルがbashからzshに変わっていることで.bash_profileを読み込まないのが原因と判明。(Mac初めて買ったのに、変わってるとかそんなん知らん...)後ほど気づいたのですが、そもそも使用不可のコマンド打ったとき、zsh: command not foundと表示されるのでちゃんと確認しておくべきでした...
ともあれ、原因わかったので再度トライ。従来のbashにも設定できるようだが、Appleがzshに切り替えたということで今後しばらくその方針になることを考えて、zshを使う方針で対応。
ターミナルのホームディレクトリに.zshrcをつくる。(vimで作業)
vim .zshrc下記を入力し保存する。
export PATH=$PATH:~/.composer/vendor/binこれでなんとかターミナルを閉じてもすぐにLaravelコマンドが使えるようになった。
- 投稿日:2020-07-26T20:46:01+09:00
ララ「13歳と294日って、どうやって求めるルン?」 on PHP
はじめに
「スター☆トゥインクルプリキュア」第2話(2019年2月10日放送)で、こんなやりとりがありました。
ひかる「ララちゃん1ってすごいよねぇ。ロケット操縦して、修理までしちゃうんだもん」
ララ「いい大人ルン。できて当然ルン」
ひかる「えっ?大人って?いくつなの?」
ララ「地球の年齢だと…」
AI「13歳と294日です」
ひかる「わたしと同い年じゃん!」さて、今ララの年齢は、地球基準で何歳と何日なのでしょうか?
それをプログラムで算出したいと思って調べたのですが、出てくるのは「何年何ヶ月と何日」というのを求めるものばかり…。
そこで、本記事では「任意の2つの日付間の経過年数と日数」をPHPで求める方法を考えたいと思います。なおPHP独自のクラスや構文が出てきますが、アルゴリズムは他の言語でも活用できると思います。
前提条件
「任意の2つの日付」を、「基準日(1)」と「基準日(2)」と表すこととします。
なお基準日(1)は、基準日(2)より前であるものとします。まず、基準日(1)ですが、同年7月7日の第22話でララの誕生日であることに触れられていること、キャラクター設定上も7月7日が誕生日とされていること、そしてひかると同じ「中学2年生」などなどから、第22話の放送の14年前に当たる「2005年7月7日」とします。
日付計算の際には、基準日(1)当日を含めないものとします。
PHPのバージョンは7.2.30を前提としていますが、7系であれば多分大丈夫だと思います。
考え方
基準日(2)によって、次の3パターンに分けます。
基準日(2)を、以下では$y_2年m_2月d_2日$とします。そして先ほどの「2005年7月7日」は次のように設定します。
y_1 = 2005, m_1 = 7, d_1 = 7基準日(1)と基準日(2)の月日が同じ場合
数式で表すと、$m_1 = m_2 かつ d_1 = d_2$の場合、となります。
この場合は単純に、$(y_2 - y_1)歳と0日$を返せば良いことになります。例)基準日(2)が「2019年7月7日」の場合
年数:$2019 - 2005 = 14$
結果:14歳と0日基準日(1)と基準日(2)の月日が違う場合(パターン1)
ここでは$y_2年m_1月d_1日$が基準日(2)より前の場合を取り上げます。
それ以外の場合は、パターン2を参照してください。この場合は、年の計算は先ほどと同様に$(y_2 - y_1)$で計算します。
あとは日付を、$y_2年y_2月y_2日 - y_2年m_1月d_1日$(初日不算入)で計算します。例)基準日(2)が「2019年7月26日」の場合:
年数:$2019 - 2005 = 14$
日数:$2019年7月26日 - 2019年7月7日 = 19$
結果:14歳と19日例)基準日(2)が「2020年7月26日」の場合:
☆2020年は閏年
年数:$2020 - 2005 = 15$
日数:$2020年7月26日 - 2020年7月7日 = 19$
結果:15歳と19日基準日(1)と基準日(2)の月日が違う場合(パターン2)
ここでは$y_2年m_1月d_1日$が基準日(2)より後の場合を取り上げます。
それ以外の場合は、パターン1を参照してください。この場合、年の計算は$(y_2 - y_1 - 1)$になります。
日付ですが、$y_2年y_2月y_2日 - (y_2 - 1)年m_1月d_1日$(初日不算入)で計算します。例)基準日(2)が「2019年4月27日」の場合
年数:$2019 - 2005 - 1 = 13$
日数:$2019年4月27日 - (2019-1)年7月7日 = 2019年4月27日 - 2018年7月7日 = 294$
結果:13歳と294日例)基準日(2)が「2020年4月27日」の場合
☆2020年は閏年
年数:$2020 - 2005 - 1 = 14$
日数:$2020年4月27日 - (2020-1)年7月7日 = 2020年4月27日 - 2019年7月7日 = 295$
結果:14歳と295日例)基準日(2)が「2019年2月27日」の場合
年数:$2019 - 2005 - 1 = 13$
日数:$2019年2月27日 - (2019-1)年7月7日 = 2019年2月27日 - 2018年7月7日 = 235$
結果:13歳と235日例)基準日(2)が「2020年2月27日」の場合
☆2020年は閏年
年数:$2020 - 2005 - 1 = 14$
日数:$2020年2月27日 - (2020-1)年7月7日 = 2020年2月27日 - 2019年7月7日 = 235$
結果:14歳と235日実装していきます
長いので折りたたむルン
datediff.php<?php /** * dateDiffの戻り値クラス */ class DateDiffResult { /** 年数 */ public $years; /** 日数 */ public $days; function __construct(int $years, int $days) { $this->years = $years; $this->days = $days; } } /** * 指定年月日の0時0分0秒のDateTimeを返す * * @param int $year 年 * @param int $month 月 * @param int $day 日 * @return DateTime */ function getDateTime(int $year, int $month, int $day) : DateTime { $date = new DateTime(); $date->setDate($year, $month, $day); $date->setTime(0, 0, 0); return $date; } /** * 基準日(1)から基準日(2)まで、何年と何日経過したかを求める(初日不算入) * * @param DateTime $inputDate1 基準日(1) * @param DateTime $inputDate2 基準日(2) * @return DateDiffResult */ function dateDiff(DateTime $inputDate1, DateTime $inputDate2) : DateDiffResult { // 各変数をセットする $y1 = intval($inputDate1->format("Y")); $m1 = intval($inputDate1->format("n")); $d1 = intval($inputDate1->format("j")); $y2 = intval($inputDate2->format("Y")); $m2 = intval($inputDate2->format("n")); $d2 = intval($inputDate2->format("j")); $date1 = getDateTime($y1, $m1, $d1); $date2 = getDateTime($y2, $m2, $d2); // 基準日(1)と基準日(2)の月日が同じ場合 if ($m1 == $m2 && $d1 == $d2) { return new DateDiffResult($y2 - $y1, 0); } // y_2年m_1月d_1日 $date3 = getDateTime($y2, $m1, $d1); // y_2年y_2月y_2日 - y_2年m_1月d_1日 $diff1 = $date3->diff($date2); if ($diff1->format("%R") == "+") { // y_2年m_1月d_1日が基準日(2)より前の場合 return new DateDiffResult($y2 - $y1, intval($diff1->format("%a"))); } else { // y_2年m_1月d_1日が基準日(2)より後の場合 $date4 = getDateTime($y2 - 1, $m1, $d1); $diff2 = $date4->diff($date2); return new DateDiffResult($y2 - $y1 - 1, intval($diff2->format("%a"))); } }テストします
長いので折りたたむルン
datedifftest.php<?php require_once("datediff.php"); use PHPUnit\Framework\TestCase; class DateDiffTest extends TestCase { // 基準日(1)と基準日(2)の月日が同じ場合 public function testDiff0() { $date1 = getDateTime(2005, 7, 7); $date2 = getDateTime(2019, 7, 7); $actual = dateDiff($date1, $date2); $this->assertEquals(14, $actual->years); $this->assertEquals(0, $actual->days); } // 基準日(1)と基準日(2)の月日が違う場合(パターン1)その1 public function testDiff1_1() { /** * 基準日(2)が「2019年7月26日」の場合: * 年数:$2019 - 2005 = 14$ * 日数:$2019年7月26日 - 2019年7月7日 = 19$ * 結果:14歳と19日 */ $date1 = getDateTime(2005, 7, 7); $date2 = getDateTime(2019, 7, 26); $actual = dateDiff($date1, $date2); $this->assertEquals(14, $actual->years); $this->assertEquals(19, $actual->days); } // 基準日(1)と基準日(2)の月日が違う場合(パターン1)その2 public function testDiff1_2() { /** * 基準日(2)が「2020年7月26日」の場合: * ☆2020年は閏年 * 年数:$2020 - 2005 = 15$ * 日数:$2020年7月26日 - 2020年7月7日 = 19$ * 結果:15歳と19日 */ $date1 = getDateTime(2005, 7, 7); $date2 = getDateTime(2020, 7, 26); $actual = dateDiff($date1, $date2); $this->assertEquals(15, $actual->years); $this->assertEquals(19, $actual->days); } // 基準日(1)と基準日(2)の月日が違う場合(パターン2)その1 public function testDiff2_1() { /** * 基準日(2)が「2019年4月27日」の場合 * 年数:$2019 - 2005 - 1 = 13$ * 日数:$2019年4月27日 - (2019-1)年7月7日 = 2019年4月27日 - 2018年7月7日 = 294$ * 結果:13歳と294日 */ $date1 = getDateTime(2005, 7, 7); $date2 = getDateTime(2019, 4, 27); $actual = dateDiff($date1, $date2); $this->assertEquals(13, $actual->years); $this->assertEquals(294, $actual->days); } // 基準日(1)と基準日(2)の月日が違う場合(パターン2)その2 public function testDiff2_2() { /** * 基準日(2)が「2020年4月27日」の場合 * ☆2020年は閏年 * 年数:$2020 - 2005 - 1 = 14$ * 日数:$2020年4月27日 - (2020-1)年7月7日 = 2020年4月27日 - 2019年7月7日 = 295$ * 結果:14歳と295日 */ $date1 = getDateTime(2005, 7, 7); $date2 = getDateTime(2020, 4, 27); $actual = dateDiff($date1, $date2); $this->assertEquals(14, $actual->years); $this->assertEquals(295, $actual->days); } // 基準日(1)と基準日(2)の月日が違う場合(パターン2)その3 public function testDiff2_3() { /** * 基準日(2)が「2019年2月27日」の場合 * 年数:$2019 - 2005 - 1 = 13$ * 日数:$2019年2月27日 - (2019-1)年7月7日 = 2019年2月27日 - 2018年7月7日 = 235$ * 結果:13歳と235日 */ $date1 = getDateTime(2005, 7, 7); $date2 = getDateTime(2019, 2, 27); $actual = dateDiff($date1, $date2); $this->assertEquals(13, $actual->years); $this->assertEquals(235, $actual->days); } // 基準日(1)と基準日(2)の月日が違う場合(パターン2)その4 public function testDiff2_4() { /** * 基準日(2)が「2020年2月27日」の場合 * ☆2020年は閏年 * 年数:$2020 - 2005 - 1 = 14$ * 日数:$2020年2月27日 - (2020-1)年7月7日 = 2020年2月27日 - 2019年7月7日 = 235$ * 結果:14歳と235日 */ $date1 = getDateTime(2005, 7, 7); $date2 = getDateTime(2020, 2, 27); $actual = dateDiff($date1, $date2); $this->assertEquals(14, $actual->years); $this->assertEquals(235, $actual->days); } // 閏年チェック(1) public function testDiff_uruu_1() { $date1 = getDateTime(2020, 2, 28); $date2 = getDateTime(2020, 3, 1); $actual = dateDiff($date1, $date2); $this->assertEquals(0, $actual->years); $this->assertEquals(2, $actual->days); } // 閏年チェック(2) public function testDiff_uruu_2() { $date1 = getDateTime(2020, 2, 28); $date2 = getDateTime(2021, 2, 1); $actual = dateDiff($date1, $date2); $this->assertEquals(0, $actual->years); $this->assertEquals(339, $actual->days); } }作りました
ここまでのものを使って、hokkaidosm.net プリキュアデータ内にララちゃんっていくつなの?を追加しました。
Mastodonでは毎日午前0時に配信予定です。
ひかるがララを呼ぶときに「ララ」と呼ぶのは、第3話の最後のほうからです。 ↩
- 投稿日:2020-07-26T20:17:44+09:00
【スクレイピングまとめ】| Python Node.js PHP Ruby Go VBA | 6種類の言語でヤフートップをスクレイピング
Python
動画
リポジトリ
https://github.com/yuzuru-program/scraping-python-yahoo
ソース
index.pyimport urllib.request as request from bs4 import BeautifulSoup req = request.Request( "https://www.yahoo.co.jp", None, {} ) instance = request.urlopen(req) soup = BeautifulSoup(instance, "html.parser") li = soup.select('main article section ul')[0].select('li') for m in li: print(m.text) print(m.select("a")[0].get("href")) print()Node.js
動画
リポジトリ
https://github.com/yuzuru-program/scraping-node-yahoo
ソース
package.json{ "dependencies": { "cheerio": "^1.0.0-rc.3", "node-fetch": "^2.6.0" } }index.jsconst fetch = require('node-fetch'); const cheerio = require('cheerio'); const main = async () => { // https://www.yahoo.co.jp/にリクエスト投げる const _ret = await fetch('https://www.yahoo.co.jp/', { method: 'get', headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36', }, referrer: '', }).catch((err) => { console.log(err); }); if (_ret.status !== 200) { console.log(`error status:${_ret.status}`); return; } // jqueryチックに使えるように変換 const $ = cheerio.load(await _ret.text()); const _li = $('main article section ul').eq(0).find('li'); // ヤフートップニュースを表示 _li.map(function (i) { console.log(_li.eq(i).text()); console.log(_li.eq(i).find('a').attr()['href']); console.log(); }); }; main();PHP
動画
リポジトリ
https://github.com/yuzuru-program/scraping-php-yahoo
ソース
index.php<?php require_once './phpQuery-onefile.php'; function my_curl($url) { $cp = curl_init(); /*オプション:リダイレクトされたらリダイレクト先のページを取得する*/ curl_setopt($cp, CURLOPT_RETURNTRANSFER, 1); /*オプション:URLを指定する*/ curl_setopt($cp, CURLOPT_URL, $url); /*オプション:タイムアウト時間を指定する*/ curl_setopt($cp, CURLOPT_TIMEOUT, 30); /*オプション:ユーザーエージェントを指定する*/ curl_setopt($cp, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'); $data = curl_exec($cp); curl_close($cp); return $data; } $url = 'https://www.yahoo.co.jp'; $doc = phpQuery::newDocument(my_curl($url)); $ul = $doc->find('main article section')->find("ul:eq(0)"); for ($i = 0; $i < count($ul->find("li")); ++$i) { $li = $ul->find("li:eq($i)"); echo $li[0]->text(); echo "\n"; echo $li[0]->find("a")->attr('href').PHP_EOL; echo "\n"; } ?>phpQuery-onefile.php
https://github.com/yuzuru-program/scraping-php-yahoo/blob/master/phpQuery-onefile.php
Ruby
動画
リポジトリ
https://github.com/yuzuru-program/scraping-ruby-yahoo
ソース
index.rbrequire "nokogiri" require "open-uri" doc = Nokogiri::HTML(open("https://www.yahoo.co.jp")) test = doc.css("main article section ul")[0].css("li") test.each do |li| puts li.content puts li.css("a")[0][:href] puts endGo
動画
リポジトリ
https://github.com/yuzuru-program/scraping-go-yahoo
ソース
index.gopackage main import ( "fmt" "log" "net/http" "github.com/PuerkitoBio/goquery" ) func main() { req, _ := http.NewRequest("GET", "http://yahoo.co.jp", nil) req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36") res, _ := new(http.Client).Do(req) if res.StatusCode != 200 { log.Fatalf("status code error: %d %s\n", res.StatusCode, res.Status) } doc, err := goquery.NewDocumentFromReader(res.Body) if err != nil { log.Println(err) } li := doc.Find("main article section ul").Eq(0).Find("li") li.Each(func(index int, s *goquery.Selection) { fmt.Println(s.Text()) tmp, err := s.Find("a").Attr("href") if err != true { log.Fatal(err) } fmt.Println(tmp + "\n") }) }VBA
動画
ソース
'Microsoft HTML Object Library 'Microsoft Internet Controls ' IEのプロセスを削除する関数 Function IeProcessKill() CreateObject("WScript.Shell").Exec ("taskkill.exe /F /IM iexplore.exe") Application.Wait Now + TimeValue("0:00:2") End Function 'ヤフートップスクレイピング Sub main() Dim ie As InternetExplorer ' IEプロセスを削除' Call IeProcessKill 'IE起動 Set ie = New InternetExplorer 'サイトを非表示 ie.Visible = False Debug.Print "読み込み中..." Debug.Print 'ヤフー ie.Navigate "https://www.yahoo.co.jp/" Do While ie.Busy = True Or ie.readyState < READYSTATE_COMPLETE Loop For Each tmp In ie.document.querySelector("main article section ul").getElementsByTagName("li") Debug.Print Trim(tmp.textContent) Debug.Print tmp.getElementsByTagName("a")(0).href Debug.Print Next tmp ' ブラウザ閉じる ie.Quit Set ie = Nothing End Sub
- 投稿日:2020-07-26T19:36:08+09:00
[それweb] アップロードされたファイルのウイルススキャンをリモートのClamAVにやらせる [でやるの?]
皆さんこんにちは。
皆さん、ウイルスチェックしてますか?
いや、自分のローカルとかじゃなくて、システム上で、アップロードされたファイルに対して、ですぜ?そんな必要あるの?って思うかもしれませんが、某R社系列の案件であれば、普通に「やってね♡」って言ってきますし、ファイルのやり取りを頻繁に行うサービスであれば、ウイルスチェックをしないといつ脅威が紛れ込むとも限りませんし、そこから情報流出なんて事態になれば、一発で会社が潰れてもおかしくないわけで、備えておく分にはやはり有用なのではないかと思ったりするわけです。
というわけで、PHPでリモートのClamAVを使ったウイルスチェックをやっていきましょう。
3行で締める
- ウイルスチェックサーバをWebサーバから切り離して運用したい
- リモートのウイルスチェックサーバにTCPで通信して、ファイルのスキャンしてもらうためのPHPライブラリを作ったのだ
- これでアップロードファイルを気軽にウイルスチェックできますね!
まあ、そんな話です
ウイルスチェックの導入
ClamAV
オープンソースのアンチウイルスソフトです。https://www.clamav.net/
Linuxで使えます。Webサーバでウイルスチェック
以前の案件ではWebサーバの中にPHPだけでなく、ウイルスチェックソフトのClamAVも入れていました。ただ、ClamAV自体は起動が遅いので、clamdというdaemonを使っていたのですが、これがとても重い。メモリをドカ食いするので、コンテナで下手にメモリ上限を入れて置こうものなら、すぐに落ちるくらい重いです。
これをWebサーバの台数だけ用意するのですから、余計なリソースがかかってしまい、経済的ではなかったのです。該当案件の方には本当に申し訳ない。
ウイルスチェックの切り離し
ウイルスチェックの機構を各Webサーバに突っ込むからいけないのであって、こいつを分離してしまえば諸々解決です。
こうしておけば、WebサーバはWebサーバの仕事のみをしていればよいわけですし、ウイルスチェックのサーバも一つあれば足りるので経済的です。
こっちの形式で実装することを考えましょう。TCPでClamAVにファイルを送ってウイルスチェックするライブラリ
なんかいいのがなかったので作りました。
https://github.com/niisan-tokyo/web-clamav-php
説明は別記事で書きます。
あるものが使えればいいんです。書くの忘れてたけど、多分
php > 7
で動くと思います。実装
簡単にアップロードしたファイルをウイルスチェックして、その結果を返すクソアプリを作ります。
環境
みんな大好き
docker-compose
で作りますよ。docker-compose.ymlversion: "3" services: workspace: build: workspace command: sleep infinity volumes: - ../:/var/www/ environment: - LANGUAGE=en_US.UTF-8 - LC_ALL=en_US.UTF-8 ports: - 8888:8888 clamav: image: dinkel/clamavd ports: - 3310clamavはdockerhubに落ちてるTCPアクセスできるclamavサーバです。これで状況的には以下の先に挙げた図の状態を再現できます。
workspaceは以下のDockerfileで作られるコンテナで、普通にcomposer入れただけのやつです。FROM php:7.4 RUN apt-get update && apt-get install -y unzip COPY --from=composer /usr/bin/composer /usr/bin/composerあとは
docker-compose up -d
でコンテナを立ち上げます。
(私の場合はVS Code Remote Container使ってます。)アプリを作る
まず、workspace内でライブラリをダウンロードします。
composer init composer require niisan-tokyo/web-clamav-phpとりあえず、これで必要なライブラリは入ったはずです。
次にフォームを作ります。
index.html<!DOCTYPE html> <head> <meta charset="utf-8" /> </head> <body> <h1>ファイルを送信</h1> <form action="file.php" enctype="multipart/form-data" method="POST"> <input type="file" name="upfile" /><br> <button type="submit" name="go">送信する</button> </form> </body>最後にアップロードされたファイルをウイルススキャンするページを作ります。
file.php<?php require 'vendor/autoload.php'; use Niisan\ClamAV\Manager; $file = $_FILES['upfile']; $scanner = new Manager(['url' => 'clamav']);// clamav のあるサーバのurlを指定。IPでも多分大丈夫 $result = $scanner->scan($file['tmp_name']);// 対象ファイルのパスを指定してスキャン ?> <!DOCTYPE html> <head> <meta charset="utf-8" /> </head> <body> <h1>問題<?php if ($result) {?> なし <?php } else {?> あり <?php } ?>!</h1> <a href="index.html">戻る</a> </body>とりあえず、こんな感じです。
ClamAVの動くサーバさえ用意できれば、実装は楽勝ですね。動作確認
では、PHPのビルトインサーバで動作確認しましょう。
php -S 0.0.0.0:8888まずはフォームにアクセスします。
http://localhost:8888/次に、フォームにファイルを突っ込んで送信します。
よさそう。
まとめ
というわけで、リモートのClamAVを使ってファイルのウイルスチェックする機構を実装しました。
今回詳しく説明しなかったライブラリの作成のほうは割と苦労したのですが、サクッと使いたい人にとってはどうでもいいと思ったので、今回は入れませんでした。
ライブラリ除いたら、実装時間は5分くらいしかかからないので、もしも「あ、アップロードされたファイルのウイルスチェックもよろしくね♡」って言われたときは、この記事を参考にしていただけると幸いです。今回はこんなところです。
- 投稿日:2020-07-26T18:43:43+09:00
組み合わせの列挙
phpで組み合わせのコードを書きたいです。
1から9までの重複のない4つの数字を使用し,それらの並びの組み合わせを出力したいです。
例えば1 2 3 4という並びの組み合わせは4!=24通りあります。
1234 1243 1324 . . . 中略 . . . 4321丸投げする気はなく、このコードを書くためのヒントを頂けたら嬉しいです。
- 投稿日:2020-07-26T17:26:54+09:00
Laravelでプロジェクト作成~MVC作成 手順、コマンドまとめ
環境
言語:php html
フレームワーク:Laravel
サーバ環境:xampp
DB:MySQL(xamppで管理)
OS:Windows 10※xampp、laravel、composer 環境構築は完了済み。
Laravelプロジェクト作成
laravel new プロジェクト名サーバ起動
php artisan servehttp://localhost:8000/
にて、Laravelページが表示されることを確認。
ここから先MVC作成
コントローラ作成
php artisan make:controller HogeControllerコントローラー追記
app/Http/Controllers/HogeController.php
public function contact() { return view("contact"); //viewファイル"contact.blade.php”呼び出し }ルーティング作成
routes/web.php
Route::get("contact", "HogeController@contact"); // http://localhost:8000/contact が呼ばれたとき、HogeController内contactメソッド呼び出しビュー作成
resources/views/contact.blade.php(手動で新規作成する)
<!DOCTYPE HTML> <html> <head> <title>contact</title> </head> <body> <h1>Hello World!!</h1> </body> </html>マイグレーション
※ユーザテーブルはデフォルトでマイグレーションファイルが作成されている。
マイグレーションファイルの作成
php artisan make:migration create_books_table名前は複数形で作成するのが慣例
マイグレーションファイル書き込み
/database/migrationsフォルダにmigrationファイルが作成される。
この中に、変数を追記する。Schema::create('books', function (Blueprint $table) { $table->id(); $table->string("title"); //追記 $table->text("body"); //追記 //etc... $table->timestamps(); });マイグレーション実行
php artisan migrateマイグレーションが成功しているかの確認
php artisan migrate:statusモデル作成
php artisan make:model Book名前は単数形で書くのが慣例(migrateは複数形)
app/ 直下に Book.phpが作成される。
- 投稿日:2020-07-26T17:19:54+09:00
Laravelで認可(Policy)
認可
簡単に言うと
認証はアクセスしてきているユーザーが誰なのかを認識するもの
認可はアクセスしてきたユーザーに操作を許可するかどうかを判定するもの
ですPolicyを使った認可
簡単に言うと
EloquentのModelに紐づく真偽値を返す関数(Policy)を作っておき、それを簡単に呼び出せるようにしておくようなものです前提条件
eclipseでLaravel開発環境を構築する。デバッグでブレークポイントをつけて止める。(WindowsもVagrantもdockerも)
本記事は上記が完了している前提で書かれています
プロジェクトの作成もapacheの設定も上記で行っていますLaravelで認可(Gate)
本記事は上記が完了している前提で書かれています
今回使うカラムも上記記事で作成済みですPolicyの作成
今回は下記記事で作成したTable2というModelを使います
Laravelでデータベースを操作する(Eloquent編)コマンドラインで
cd sample
php artisan make:policy Table2Policy --model=Models\Table2
xdebugの設定をしているとeclipseが実行していいですかというプロンプトを出すのでOKを押します
eclipseプロジェクトを右クリック→リフレッシュ
/sample/app/Policies/Table2Policy.phpが現れます/sample/app/Policies/Table2Policy.php修正
Table2Policy.php‥‥ public function create(User $user) { return $user->rank >= 2; } public function update(User $user, Table2 $table2) { return $user->rank >= $table2->int_col; } ‥‥createメソッドとupdateメソッドを修正しました
Policyのメソッドの第一引数は必ず、ユーザーインスタンスになります下記7メソッドが自動的にできあがっていると思います
viewAny
view
create
update
delete
restore
forceDelete
これらはコントローラーをつくるときにphp artisan make:controller XXXController --resource
のように--resource
オプションを付けて作成したリソースコントローラーの各メソッドに対応しています
コントローラメソッド ポリシーメソッド index viewAny show view create create store create edit update update update destroy delete コントローラーのindexメソッドではPolicyのviewAnyメソッドを使ってください
コントローラーのshowメソッドではPolicyのviewメソッドを使ってください
ということですPolicyの登録
/sample/app/Providers/AuthServiceProvider.php修正
AuthServiceProvider.php‥‥ protected $policies = [ 'App\Models\Table2' => 'App\Policies\Table2Policy', ]; ‥‥
$policies
配列に要素を追記しました
keyがModel、valueがPolicyになります
これでLaravelアプリケーションの中でPolicyを簡単に呼び出せるようになりますControllerにメソッド追加
(1) use文追記
use Illuminate\Support\Facades\Auth;
use App\Models\User;
use App\Models\Table2;
(2) /sample/app/Http/Controllers/SampleController.phpにgate1メソッド、gate2メソッドを追記
public function policy1() { return view('sample.policy1'); } public function policy2(Request $request) { $int_col = $request->input('int_col'); $user = Auth::user(); $table2 = new Table2(); $table2->int_col = $int_col; if (is_null($user) || !$user->can('create', Table2::class)) { return redirect('sample/policy1')->withInput(); } if (is_null($user) || !$user->can('update', $table2)) { return redirect('sample/policy1')->withInput(); } $data = ['table2' => $table2]; return view('sample.policy2', $data); }Policyは
$user->can
で呼べます
第一引数に与えた文字列はPolicyに定義したメソッド名です
第二引数はPolicyに定義したメソッドの第二引数になります
第二引数を定義していないPolicyの場合第二引数にはクラス名を渡します
Policyの第一引数は$user
になります
$user->can
はPolicyが返す真偽値がそのまま戻り値になります(2) /sample/routes/web.phpに下記を追記
Route::get('sample/policy1', 'SampleController@policy1');
Route::get('sample/policy2', 'SampleController@policy2');
viewの作成
(1) /sample/resources/views/sample/policy1.blade.php
policy1.blade.php<html> <head> <title>sample</title> </head> <body> <form action="{{ url('sample/policy2') }}" method="get"> <div>int_col<input type="text" name="int_col" value="{{ old('int_col') }}"></div> <input type="submit" > </form> </body> </html>(2) /sample/resources/views/sample/policy2.blade.php
policy2.blade.php<html> <head> <title>sample</title> </head> <body> <div> @can('create', App\Models\Table2::class) can create @elsecan('update', $table2) can update @else else @endcan </div> <div> @cannot('create', App\Models\Table2::class) cannot create @elsecannot('update', $table2) cannot update @else else @endcannot </div> </body> </html>blade内でPolicyを使うにはcanやcannotで呼び出せます
@can
や@cannot
を使うことで、更新ボタンを特定のユーザーにだけ表示させたり、または、逆に非表示にしてりできます動作確認
usersテーブルのrankカラムの値を適当な値にupdateします
update users set rank = 4;ログインします
http://localhost/laravelSample/
右上のLOGINからログインします
http://localhost/laravelSample/sample/policy1
rankを4にupdateしてので
5以上の値を入力して送信ボタンをクリックするとpolicy1にリダイレクトされ先に進めないと思います
4以下の値を入力して送信ボタンをクリックするとpolicy2に遷移できます
今回はサンプルのため入力値のエラーチェックを一切行っていないので、gate1の入力欄には数値を入れてください。数値でない場合、制御されていない型エラーになります
実際にシステムを開発するときはちゃんとエラーチェックを入れましょう
エラーチェックの実装の仕方は下記に書いてあります
Laravelで入力値エラーチェック(validate)を実装する
- 投稿日:2020-07-26T16:12:46+09:00
エンジニア歴10ヶ月目で「PHP7技術者認定初級試験」を受けてみた
はじめに
2019年の9月にエンジニアに転職して、約10ヶ月が経過しました。
PHPも業務で初めて触ったのでエンジニア歴とPHP歴はほぼイコールです。ネットで色々調べていたら、「PHP7技術者認定初級試験」という検定があることを知りました。
実際に試験を受けて合格したので、
どのような試験で、どのくらいの期間何を試験対策として行ったのかを簡単に共有できればと思います。
- この試験を受けるか迷ってる方
- PHPを触り初めて1年くらいの人で自分の理解度の把握をしたいと思われている方
- 基本構文の習得程度で、FWでPHPを書いている方 などの参考になれば幸いです。
PHP7技術者認定初級試験とは
一般財団法人PHP技術者認定機構が実施している、
PHP技術の習得度合いを測るための試験です。体系だてたPHP学習の総チェックにいかがですか?という感じで、
検定試験の部類で資格試験ではないです。PHP5とPHP7の試験があり、
PHP5は上級試験/準上級試験の2つ
PHP7は初級試験のみ
という構成です。私はPHP7初級試験を受けました。
試験内容はPHPの仕様や基本構文、設定周りなど基礎的な部分となります。
正誤選択形式で、試験会場のPCで受験します。
60分で40問。
7割正解で合格。
試験料は13,200円(税込)
結果は試験終了後にすぐ確認できます。出題範囲はオライリーの『初めてのPHP』(PHP7版)の内容に沿って出題されます。
実際の出題内容については公言できないので、詳細な出題範囲は公式でチェックしてみてください。
https://www.phpexam.jp/summary/novice7/
業務でPHPを使用していれば、問題集を何周かすれば合格できると思います。
試験対策
出題された問題については言及できませんので、
実際に試験当日までどのくらいの期間で何をやったのかをまとめます。
私の試験の結果は850点/1000点でした。対策期間
- 2週間(月~金で各日15分~30分程度、土日で2時間程度)
やったこと
- 下記の問題集を3周
徹底攻略PHP7技術者認定[初級]試験問題集 徹底攻略シリーズ
https://www.amazon.co.jp/dp/B07V2MK63C/ref=cm_sw_em_r_mt_dp_U_E8rhFbRSVEMS6- 独習PHP 第3版を1周通読
https://www.amazon.co.jp/dp/B01FH3KVNU/ref=cm_sw_em_r_mt_dp_U_b-rhFbPCFQ8ZSおそらく業務でPHPに触っている方は試験問題集をやり込めば問題なく合格レベルまで達せると思います。
その他にPHPの基礎的な書籍で問題集での説明が不十分な箇所や自分の理解が浅いところを補完するとより完璧に近づきます。
公式の出題範囲は初めてのPHPですが、基本的な内容は同じなので同一の書籍がなくても問題ないと思います。
実際に私は独習PHPという書籍を持っていたのでそちらを使用しましたが、全く問題ありませんでした。この試験でPHPに初めて触れるという方は初めてのPHP→試験問題集の順番で進めるのがいいと思います。
対策のポイント
初級試験の内容は基礎的なもので、決して難しい内容ではありません。
基礎構文や条件分岐、繰り返し、配列の操作などは
業務で1年程度PHPでコードを書いていれば改めて勉強する必要はないと思います。しかし、文章での正誤選択問題となるので細かい部分の理解が必須となります。
実際に問題集や教材で出てくるコードは実際に動かして一つ一つ確認して行くのが大切です。
特に組み込みメソッドの返り値などはしっかり把握する必要があります。苦労したポイント
私はPHPの基本的な構文を最低限覚え、その後業務でFWでの開発に移ってしまいました。
そのため、PHPの仕様(動作環境)やPHPコマンド、設定周り、など根本的な部分の理解がなく、試験対策を通じて知ることが多かったです(恥ずかしい...)。実際に問題集の1周目は基本的な条件分岐、ループ、配列操作、webフォームでのやり取り、DB操作、CookieとSessionなど以外は全然解けませんでした。
公式の出題範囲でいうと下記の部分の知識が完全に抜け落ちていました。
- PHPの特徴
- ファイルの操作
- 情報の保存:データベース(PDOのオプションやプリペアドステートメント等の部分)
- ファイルの操作
- メールの送信
- コマンドラインPHP(サーバ起動やドキュメントルートの設定,php.iniや構成ディレクティブあたり)
この辺りは私のようにすぐにFW中心の開発に移ってしまった方は抜けている部分が多いと思います。
オプション指定などについても細かく聞かれるので、問題集に出ている部分は確実に習得する必要があります。
本番の試験は問題集の出題より少しだけ難しく感じました。
より迷わせる問題が多く、問題集の内容を暗記だけではなくしっかり理解しているかを突いてくる問題が多かったです。まとめ
実際にこの試験を受けてみて、PHPのことを全然知らなかった/理解していなかったと痛感しました。
「言語そのものの理解」を深めることの重要性も知ることができました。
試験対策をして、PHPについての理解が深まったことでFWを使っていてモヤモヤしていた部分がすっきりと理解できる、使えるようになった部分もたくさんありました。特に有名な試験でもないし、合格したからと言って自身の価値向上に繋がるような試験ではないですが、
言語について再度しっかり学びたい方、これから習得を目指す方にはとてもいい試験だと思いました。試験を通してPHPにより興味を持ったので、継続して言語について理解を深めていこうと思っています。
最後まで読んで頂きありがとうございました。
- 投稿日:2020-07-26T16:08:23+09:00
【PHP】Youtube Data APIによる動画情報取得
Youtube Data APIを試してみました。
PHPの環境があれば、数分で実行まで行けるんじゃないかと思います。
環境:Windows
言語:PHPコマンドプロンプトで実行できるものです。
これを、ファンクション化したりと、使いまわしできたらいいんじゃないかなと。参考サイト
事前準備
まず、必要なライブラリをインストール 対象のphpファイルを置くディレクトリとかで。
composer require google/apiclient:^2.0
APIキーの取得
こちらのAPIキーの取得の手順が参考になります。
APIキーを生成したら、メモっておいてください。証明書の配置
以下のURLの「証明書の配置」が参考になります。
http://azwoo.hatenablog.com/entry/2017/12/15/004353
cacert.pemをダウンロードし、
「C:\php\extras\ssl\cacert.pem」のように、PHPインストールフォルダ内に配置してください。コード
youtubeApitest.php<?php require_once __DIR__ . '/vendor/autoload.php'; const MAX_SNIPPETS_COUNT = 5; const DEFAULT_ORDER_TYPE = 'date'; const DEFAULT_TYPE = 'video'; const CHHANNEL_ID = 'UCL6JY2DXJNDOIqCP1CRADng';//某ちゃんねる const API_KEY = "AIzaSyA-YBJhfKrI-aaaaaaaaaaaaaaaaaaaa"; // Googleクライアントのインスタンス $http = new GuzzleHttp\Client([ 'verify' => 'C:\php\extras\ssl\cacert.pem' ]); $client = new Google_Client; $client->setHttpClient($http); $client->setApplicationName("youtube-api-data-test"); $client->setDeveloperKey(API_KEY); // Youtubeのインスタンス $youtube = new Google_Service_YouTube($client); //ここに好きなYouTubeのチャンネルIDを入れる $params['channelId'] = CHHANNEL_ID; $params['type'] = DEFAULT_TYPE; $params['maxResults'] = MAX_SNIPPETS_COUNT; $params['order'] = DEFAULT_ORDER_TYPE; try { $searchResponse = $youtube->search->listSearch('snippet', $params); } catch (Google_Service_Exception $e) { echo htmlspecialchars($e->getMessage()); exit; } catch (Google_Exception $e) { echo htmlspecialchars($e->getMessage()); exit; } foreach ($searchResponse['items'] as $searchResult) { switch ($searchResult['id']['kind']) { case 'youtube#video': $videos = sprintf('<li>%s (%s)</li>', $searchResult['snippet']['title'], $searchResult['id']['videoId']); echo "タイトル:".$searchResult['snippet']['title']." URL:https://www.youtube.com/watch?v=".$searchResult['id']['videoId']; break; } }実行コマンド
php youtubeApitest.php補足
この定数に、上記の事前準備で取得したAPIキーをセットします。
const API_KEY = "AIzaSyA-YBJhfKrI-aaaaaaaaaaaaaaaaaaaa";
Googleクライアントの初期化をします。上記の「証明書の配置」で配置したパスを「verify」で指定します。
$http = new GuzzleHttp\Client([ 'verify' => 'C:\php\extras\ssl\cacert.pem' ]); $client = new Google_Client; $client->setHttpClient($http); $client->setApplicationName("youtube-api-data-test"); $client->setDeveloperKey(API_KEY);これをやらないと、以下の感じがでます。コンポーザーでインストールした「google/apiclient」のバージョンも影響あるらしく、エラー解消しない場合は、再インストールを・・。(自分はそれで解消)
PHP Fatal error: Uncaught exception 'GuzzleHttp\Exception\RequestException' with message 'cURL error 60: SSL certificate problem: unable to get local issuer certificate (see http://curl.haxx.se/libcurl/c/libcurl-errors.html)' in C:\develop\ga-php\vendor\guzzlehttp\guzzle\src\Handler\CurlFactory.php:187条件指定をします。パラメータについては、リファレンス Videosの「プロパティ」を見てみてください。
$params['channelId'] = CHHANNEL_ID; $params['type'] = DEFAULT_TYPE; $params['maxResults'] = MAX_SNIPPETS_COUNT; $params['order'] = DEFAULT_ORDER_TYPE;取得結果です。echoの所で、動画タイトルとURLを出力しています。取得内容については、先ほどのリファレンス Videosの「プロパティ」を見てみてください。
foreach ($searchResponse['items'] as $searchResult) { switch ($searchResult['id']['kind']) { case 'youtube#video': $videos = sprintf('<li>%s (%s)</li>', $searchResult['snippet']['title'], $searchResult['id']['videoId']); echo "タイトル:".$searchResult['snippet']['title']." URL:https://www.youtube.com/watch?v=".$searchResult['id']['videoId']; break; } }
- 投稿日:2020-07-26T16:06:07+09:00
【PHP】問題集への解答
ID 10
200万までの素数の合計値を求める
<?php $start = microtime(true); play(); $end = microtime(true); print "\n".'処理時間 = ' . ($end - $start) . '秒'."\n" ; function play() { printf("%8d",2); $sum=2; for($i=3;$i<=2000000;$i+=2) { $k=0; for($j=3;$j<=sqrt($i);$j+=2) { if($i%$j==0) { $k=1; break; } } if($k==0) { // printf("%8d",$i); $sum+=$i; } } print "\n\n\n$sum\n\n\n" ; }ID 16
2^{15}=32768の3+5+2+7+6+8=26 では2^{1000}はなにかとりあえず2の1000乗を見てみると
print 2**1000;1.0715086071863E+301指数表記される。
E+N
というのは10のN乗という意味
1.0715086071863E+301
は1.0715086071863☓10^{301}という意味。
<?php $start = microtime(true); play(); $end = microtime(true); print "\n".'処理時間 = ' . ($end - $start) . '秒'."\n" ; function play(){ $num=2**500; var_dump($num); if(strpos((string)$num,'E') !== false){ //'abcd'のなかに'bc'が含まれている場合 preg_match('/(.*)E/',(string)$num,$match); $num=$match[1]; $num=str_replace('.', '', $num); } var_dump($num); preg_match_all('/(\d)/',(string)$num,$match); print_r((array_sum($match[0]))); }53だと思ったが違うみたい
- 投稿日:2020-07-26T15:49:58+09:00
日本語諸方言コーパスをDB化して遊ぶ (3) PHP Laravel で操作する
データベースに慣れるために、「日本語諸方言コーパス (COJADS) 」の元データをデータベース化してみました。第3回は前回作成した SQLite3 データベースを PHP Laravel で操作するところまで。完全に自分用の作業メモで、説明もいろいろ足りていないと思いますが、ご容赦ください。
Laravel の設定
Laravel は PHP のフレームワークのひとつです。そんな凝ったものは作らないので素の PHP でもいいんですが、Laravel の案件も多いし、せっかくなので Laravel を使ってみます(使ったことないけど、Ruby on Rails みたいなもんだろうという軽い気持ち)。
PHP と Laravel の導入
Laravel は PHP のフレームワークなので、まず PHP 環境を作って、依存関係管理の Composer を利用して Laravel を導入していきます。PHP 環境は XAMPP にしました。Windows 環境なのでインストーラーでさくっとインストールします(今回は PHP 7.4.8、Laravel 7.21.0 を使用しています)。
SQLite の有効化
SQLite をデータベースとして使えるように PHP の設定を変更します。私の環境では XAMPP でインストールした時点で有効化されていましたが、念のため php.exe と同じフォルダの設定ファイル php.ini を確認して、以下の行が
;
でコメントアウトされていないことを確認しておきます。php.iniextension=pdo_sqliteプロジェクトの作成
Composer で適当な場所に Laravel プロジェクトを作成します。今回は XAMPP フォルダに
_projects
フォルダを作成し、その中でcojads
プロジェクトを管理することにします。cmdC:\xampp\_projects>composer create-project laravel/laravel --prefer-dist cojadscojads プロジェクトの設定から、データベースとして SQLite を使用するように変更しておきます。
DB_CONNECTION
をsqlite
にして、それ以降のデータベースの設定項目は不要なので、削除するかコメントアウトします。.env内DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=homestead DB_USERNAME=homestead DB_PASSWORD=secret ↓↓↓次のように変更する↓↓↓ DB_CONNECTION=sqlite #DB_HOST=127.0.0.1 #DB_PORT=3306 #DB_DATABASE=homestead #DB_USERNAME=homestead #DB_PASSWORD=secret
/config/database.php
のデフォルト設定も、mysql になっているので sqlite に変更しておきます。config/database.php内'default' => env('DB_CONNECTION', 'mysql'), ↓↓↓次のように変更する↓↓↓ 'default' => env('DB_CONNECTION', 'sqlite'),データベースの設定
既存データベースのマイグレーション
まずはマイグレーションを行ないます。前回作成した
cojads.sqlite3
をdatabase.sqlite
に改名して 1プロジェクトのdatabase
フォルダに配置します。マイグレーションはphp artisan migrate
コマンドで行なえます。cmdC:\xampp\_projects\cojads>php artisan migrate Migration table created successfully. Migrating: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_000000_create_users_table (0.32 seconds) Migrating: 2019_08_19_000000_create_failed_jobs_table Migrated: 2019_08_19_000000_create_failed_jobs_table (0.13 seconds)モデルの作成
次に Model を設定します。初期段階では Model 用のフォルダもないので、ひとまず
app
フォルダにModels
フォルダを新規作成し、以下のようにテーブルごとにモデルを作成していきます。cmdphp artisan make:model Models/prefecture php artisan make:model Models/location php artisan make:model Models/speaker php artisan make:model Models/discourse php artisan make:model Models/utteranceそうすると、
Models
フォルダ内にそれぞれのモデルのクラス設定ファイルが作成されています。作成した段階ではクラス宣言のみで何の役にも立たないので、たとえばspeaker
テーブルなら以下のように属性とゲッターメソッドを書き込んでいきます。$guarded
には主キーspeakerId
を設定します。app/Models/speaker.php<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\DB; class Speaker extends Model{ protected $table = 'speaker'; protected $guarded = array('speakerId'); public $timestamps = false; public function getData(){ $data = DB::table($this->table)->get(); return $data; } }ルーティングとサンプルページの作成
今回はデータベースの中身を閲覧できるシンプルなページを作ります。今回は「話者 (speaker) 」を閲覧できるページを作りましょう。まず
routes/web.php
にルーティングを追加します。以下ではcojads/speaker
に GET アクセスするとCojadsController
コントローラーのspeaker
アクションを実行するようにしています。web.phpRoute::get('cojads/speaker', 'CojadsController@speaker');次にルーティングに沿って
CojadsController
コントローラーを新規作成して……cmdphp artisan make:controller CojadsControllerさっき指定した
Speaker
モデルに関するspeaker
アクションだけ作ります。ここではデータを取得してcojads.speaker
ビュー、つまりviews/cojads
フォルダの中のspeaker
という名前のファイルに値を送るようにしています。app/Http/Controllers/CojadsController.php<?php namespace App\Http\Controllers; use App\Models\Speaker; class CojadsController extends Controller{ public function speaker(){ $md = new Speaker(); $data = $md->getData(); return view('cojads.speaker', ['data' => $data]); } }最後に今のアクションの指定に沿って
/resources/views
フォルダにcojads
フォルダを作り、その中にビューを作成していきます。ここでは blade 記法を使うのでspeaker.blade.php
を作成して、以下のようにテーブルを作ってspeaker
テーブルの内容を順に描画していきます。/resouces/views/cojads/speaker.blade.php<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Show Speakers</title> </head> <body> <table> <thead> <tr> <th>話者ID</th> <th>性別</th> <th>生年</th> </tr> </thead> <tbody> @foreach($data as $d) <tr> <td>{{$d->speakerId}}</td> <td>{{$d->speakerSex}}</td> <td>{{$d->speakerBirthyear}}</td> </tr> @endforeach </tbody> </table> </body> </html>ここまでの作業が済んだら、正しく表示されるか確認します。
php artisan serve
を実行してローカルサーバを立ち上げて……C:\xampp\_projects\cojads>php artisan serve Laravel development server started: http://127.0.0.1:8000先ほどルーティングした通り
http://127.0.0.1:8000/cojads/speaker
にアクセスします。きちんと表示されていますね( CSS は適当にあてています)。
次回予告
次回は検索機能など、もうちょっとリッチな機能を実装します。
これは Laravel がデフォルトで
database
フォルダのdatabase.sqlite
を参照する設定になっているからです。ファイル名を変えたくない場合は、config/database.php
の内でdatabase.sqlite
を参照している部分を任意のファイル名に変更するか、ルートの.env
内でデータベースの場所をフルパスで指定する必要があります。たとえばDB_DATABASE=C:\xampp\_projects\cojads\database\cojads.sqlite3
など。 ↩
- 投稿日:2020-07-26T14:44:17+09:00
【PHP】素数の計算
素数計算を高速化する。
こちらの記事を自分なりに備忘録としてまとめました。1万以下の素数のコード
最初に書いたコード
$start = microtime(true); play(); $end = microtime(true); print "\n".'処理時間 = ' . ($end - $start) . '秒'."\n" ; function play() { for ($i=max($prime)+1;$i<100000; $i++) { if (checkPrime($i)) { print $i."\n"; } } } function checkPrime($num) { $count=0; for ($i=2; $i <$num ; $i++) { if ($num%$i==0) { return false; } } return true; }2つの関数で生成する方法しか思いつかなかった
処理時間
処理時間 = 19.969306945801秒約20秒かかった。
最初から考えて作り直す
素数は約数が1と自分自身のみ
その数が1と自分自身の2回だけ割り切れたならそれは素数
for ($i=1; $i <10000 ; $i++) { // print $i."\n"; $yakusuu=0; for ($j=1; $j <=$i ; $j++) { if ($i%$j==0) { $yakusuu++; } } if ($yakusuu==2) { print $i."\n"; // break; } }処理時間
処理時間 = 1.4896070957184秒高速化
素数の約数は1と自分自身のみ
ということは奇数のみをループにかければいい
もちろん2は除く。printf('%5d',2); for ($i=3; $i <10000 ; $i+=2) { // print $i."\n"; $yakusuu=0; for ($j=3; $j <=$i ; $j+=2) { if ($i%$j==0) { $yakusuu++; } } if ($yakusuu==1) { printf('%5d',$i); // break; } } print "\n"; ##処理時間処理時間 = 0.39670991897583秒
```
- 投稿日:2020-07-26T13:43:55+09:00
LaravelでAPIを作成
- 投稿日:2020-07-26T13:37:02+09:00
HerokuにデプロイしたLaravelアプリをURL正規化する
はじめに
HerokuにデプロイしたLaravelのWebサービスをURL正規化しようとしましたが、
.htaccessのリダイレクトが有効にならずにハマったのでMiddlewareでリダイレクトしました。やりたいこと
- 「/index.php」を「/」にリダイレクトしたい
- Herokuドメイン「***.herokuapp.com」を独自ドメイン「***.com」にリダイレクトしたい
この記事で説明しないこと
- Herokuの独自ドメイン設定
- 独自ドメインSSL化
環境
Laravel
$ php artisan --version Laravel Framework 7.19.0作業内容
URL正規化用のMiddleware登録
GithubからMiddlewareをダウンロード
GithubにLaravelでURL正規化する為のMiddlewareを公開されている方がいたので使用させて頂きました。
- 参考:Laravel Middleware で URL を正規化する例です。ダウンロードしたファイルを以下フォルダへコピーします。
. ├── app │ └── Http │ └── Middleware │ └── Normalize.phpNormalize.phpを変更
handle()
先頭に独自ドメインへのリダイレクトを追加します。
※ドメインの***の部分は読み替えてください。/app/Http/Middleware/Normalize.php/** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { // ここから if ($_SERVER['HTTP_HOST'] == '***.herokuapp.com') { return redirect()->to('https://***.com' . $request->getRequestUri(), 301, [], $secure = true); } // ここまで $url = $_SERVER['REQUEST_URI']; // PHP 標準のリクエストを使用 $normalized = $this->normalize($url); if ($url != $normalized) { header('Location: ' . $normalized, true, 301); // PHP 標準のリダイレクトを使用 exit(); } ....normalize()
今回はパスの末尾をスラッシュで置き換える必要が無かったためスラッシュ追加部分をコメントアウトしました。
また、ローカルではlocalhost:8080ポートで動かしているためhostとportを追加しました。/app/Http/Middleware/Normalize.php/** * @param $url * @return string */ private function normalize($url) { // 'host'、'port'を追加 $elements = ['scheme', 'host', 'port', 'path', 'query', 'fragment']; $isHtmlEncoded = $url !== ($decoded = htmlspecialchars_decode($url)); $url = $decoded; $parsed = parse_url($url); foreach ($elements as $element) { $parsed[$element] = $parsed[$element] ?? ''; } parse_str($parsed['query'], $params); preg_match('/(.*)\/(.*)/', $parsed['path'], $matches); $dirname = $matches[1] ?? ''; $dirname .= '/'; $basename = $matches[2] ?? ''; /* 今回不要のためコメントアウト // パス末尾に / を追加 if ($basename !== '' && mb_strpos($basename, '.') === false) { $dirname .= $basename . '/'; $basename = ''; } */ // index.* を削除 $basename = preg_replace('/^index\.(.*)/', '', $basename); // パスの // -> / 置き換え $dirname = preg_replace('/\/\/+/', '/', $dirname); // クエリ並び替え ksort($params); $parsed['scheme'] .= empty($parsed['scheme']) ? '' : '://'; // 'port'を追加 $parsed['port'] = empty($parsed['port']) ? '' : ':'.$parsed['port']; ....Middlewareを登録
Normalizeをグローバルミドルウェアとして登録します。
/app/Http/Kernel.phpprotected $middleware = [ .... \App\Http\Middleware\Normalize::class, // <= 追記 ];確認
あとはHerokuへデプロイしリダイレクトが確認できれば完了です。
参考にしたサイト
- 投稿日:2020-07-26T13:37:02+09:00
HerokuにデプロイしたLaravelアプリのURLを正規化する
はじめに
HerokuにデプロイしたLaravelのURLを正規化しようとしましたが、
.htaccessのリダイレクトが有効にならずにハマったのでMiddlewareでリダイレクトしました。やりたいこと
- 「/index.php」を「/」にリダイレクトしたい
- Herokuドメイン「***.herokuapp.com」を独自ドメイン「***.com」にリダイレクトしたい
この記事で説明しないこと
- Herokuの独自ドメイン設定
- 独自ドメインSSL化
環境
Laravel
$ php artisan --version Laravel Framework 7.19.0作業内容
URL正規化用のMiddleware登録
GithubからMiddlewareをダウンロード
GithubにLaravelでURL正規化する為のMiddlewareを公開されている方がいたので使用させて頂きました。
- 参考:Laravel Middleware で URL を正規化する例です。ダウンロードしたファイルを以下フォルダへコピーします。
. ├── app │ └── Http │ └── Middleware │ └── Normalize.phpNormalize.phpを変更
handle()
先頭に独自ドメインへのリダイレクトを追加します。
※ドメインの***の部分は読み替えてください。/app/Http/Middleware/Normalize.php/** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { // ここから if ($_SERVER['HTTP_HOST'] == '***.herokuapp.com') { return redirect()->to('https://***.com' . $request->getRequestUri(), 301, [], $secure = true); } // ここまで $url = $_SERVER['REQUEST_URI']; // PHP 標準のリクエストを使用 $normalized = $this->normalize($url); if ($url != $normalized) { header('Location: ' . $normalized, true, 301); // PHP 標準のリダイレクトを使用 exit(); } ....normalize()
今回はパスの末尾をスラッシュで置き換える必要が無かったためスラッシュ追加部分をコメントアウトしました。
また、ローカルではlocalhost:8080ポートで動かしているためhostとportを追加しました。/app/Http/Middleware/Normalize.php/** * @param $url * @return string */ private function normalize($url) { // 'host'、'port'を追加 $elements = ['scheme', 'host', 'port', 'path', 'query', 'fragment']; $isHtmlEncoded = $url !== ($decoded = htmlspecialchars_decode($url)); $url = $decoded; $parsed = parse_url($url); foreach ($elements as $element) { $parsed[$element] = $parsed[$element] ?? ''; } parse_str($parsed['query'], $params); preg_match('/(.*)\/(.*)/', $parsed['path'], $matches); $dirname = $matches[1] ?? ''; $dirname .= '/'; $basename = $matches[2] ?? ''; /* 今回不要のためコメントアウト // パス末尾に / を追加 if ($basename !== '' && mb_strpos($basename, '.') === false) { $dirname .= $basename . '/'; $basename = ''; } */ // index.* を削除 $basename = preg_replace('/^index\.(.*)/', '', $basename); // パスの // -> / 置き換え $dirname = preg_replace('/\/\/+/', '/', $dirname); // クエリ並び替え ksort($params); $parsed['scheme'] .= empty($parsed['scheme']) ? '' : '://'; // 'port'を追加 $parsed['port'] = empty($parsed['port']) ? '' : ':'.$parsed['port']; ....Middlewareを登録
Normalizeをグローバルミドルウェアとして登録します。
/app/Http/Kernel.phpprotected $middleware = [ .... \App\Http\Middleware\Normalize::class, // <= 追記 ];確認
あとはHerokuへデプロイしリダイレクトが確認できれば完了です。
参考にしたサイト
- 投稿日:2020-07-26T11:30:44+09:00
障害・エラー発生時に原因を特定するための勘所まとめ(WEBエンジニア向け)
こんな人に読んで欲しい
- サーバーサイドエンジニアの人
- 障害・エラー発生時にあわわわわってなってしまう人
- 担当しているサービスをあんまり理解してないから調べ方わからん、と言う人
- どこから調べればよいかわからない人
何について話す?
WEBエンジニアとして、サービスを提供する開発をしていると
- リリース時
- 運用中
- 何もしていないのに突然
などエラーや障害が発生することがあるでしょう。
自分が所属するチームでは一つのコンテンツを長く管理しつつ、
他チーム離散など政治的事情で新しいコンテンツを受け入れる機会が多いです。
新しいコンテンツを受け入れた際に、突然来ます、エラーが、障害が・・・そんなエラーが発生した際にコンテンツへの理解がなくても原因を特定できた、
という機会がままあります。
(テーブル構成とかアーキテクトとか全く知らないけど、エラーを見つけて対応もできた、とか)そのような経験を通して、コンテンツへの理解があるからこそ調べられる調査と、
理解がなくても調べられる調査があると感じました。よくわからないコンテンツでもある程度の調査ができる勘所を紹介していきます。
調査能力を身に付けるための技術を身に付ける
コンテンツへの理解がなくても調べられる、と言いましたが技術的な知識は必要です。
AWSを扱っているのであれば「CloudWatchLogsってなんぞ?」
Linuxサーバで構築しているのであれば「apacheってなんぞ?log出力されているところってどこぞ?」
などと言われてしまうと調査ハードルは格段に上がると思います。以下の資格を取れる程度の知識を身に付けておくことをおすすめします。
- AWSソリューションアーキテクト(最低限クラウドプラクティショナー程度)
- LinuC(最低限Level1程度)
これらの知識がなくてもこれから提案する調査の勘所は参考になると思うので、
引き続き見てもらえますと幸いです。障害発生時フェーズ
1.緊急レベルを判断する
まずこのエラーは即時対応する必要があるのか、今週中に対応すればいいのか、
それとも放置してもいいものなのかを判断する必要があります。
会社などで決められているルールがあればそれを遵守すればいいですが、自分であれば以下のような基準で考えています。即時対応レベル
- サービス提供が不可能な状態が発生している(操作できない・不正データが登録される等)
- サービスは提供できているが利用者のユーザビリティを著しく落としている(画面の表示崩れ等)
- 簡易的な攻撃で不正動作ができてしまう状態(パラメータ操作やテキストエリアでSQLインジェクションができる等)
近日対応レベル
- 社内管理画面、運用をする際に弊害があるが、代替手段が取れる状態
- ユーザへのサービスに影響がないエラーログ等が出ている(Noticeエラーなど)
放置レベル
- ユーザーが使う頻度が極端に少ないコンテンツでサービス提供はできている状態でのエラー
- バッチなどで自動リカバリする仕組みが存在しているもの
2.上司・関係者に報告する
エラーレベルを判断したら関係者に報告しましょう。
上記で緊急対応レベルとわかったら即座に報告した方がいいです。
このような報告を怠ってエラー解消に尽力しがちですが、あとで何故すぐに報告しなかったんだ!!となってしまいます。障害発生を関係者が検知できていないと、関係者が行うべき対策が遅れてしまうことになります。他の人にお願いするでもいいので報告しましょう。また、自分起因のエラーが発生した時に
この事象を隠蔽することができないか
頭をよぎることがあると思います。
自分も何度も頭をよぎりますが、これは隠蔽しようとしてばれた時が最も怖いです。
自身に対する信頼が失墜します。誠実さを示して関係者に正直に報告しましょう。ログ調査フェーズ
ここからが調査についてです。
1.障害が発生しているサーバーを特定する(知っている場合は省略可)
心当たりがあるサーバに接続して以下の部分を調べます。
apache設定を見て、対象コンテンツのディレクトリ設定があれば対象サーバになります。
apache設定がそもそもどこにあるかも特定する必要がありますが、以下で動いている可能性が高いです。
OSとインストール方法 ディレクトリ CentOS,RHEL,Fedoraなど(パッケージ) /etc/httpd/conf/ SUSE,Debian,MacOSなど(パッケージ) /etc/apache2/ ソースインストール /usr/local/apache2/conf/ 以下のコマンドを入力すればある程度のapacheの場所がわかると思います。
$ ps aux | grep httpd
▼参考Qiita
Apacheの設定ファイル httpd.confの場所2.ログ出力の場所を特定する
対象ソースの中からログをどこに吐き出しているかソースをgrepしましょう。
ログ設定は以下のようなパターンが多いと思います。
- アプリケーション独自に定義している
- プログラミング言語のフレームワークの設定で定義している
- apacheでログ設定されている
- 各ミドルウェア・プログラミング言語のデフォルトのログ出力場所(/var/log/php等)
もしもソースにそのような設定が無かった場合、apacheを見たり会社独自のナレッジサイトがあるならそこから調査するのもいいと思います。
3.ログファイルから対象エラーログを特定する
対象のディレクトリ設定までわかれば、実際にログファイルの中で原因となるエラーを特定します。
もし対象ディレクトリ内に複数ファイルがあってどのファイルかわからない場合、障害が起きているサービスを動かしながら以下のコマンドを実行するといいです。
find [ログディレクトリ] -mmin -10
このコマンドを実行することで、対象サービスを動かした際にログが出力されたファイルが何なのか、ある程度特定することができます。
対象のファイルがわかったらファイル内をgrepしてみましょう。
grepの仕方は様々ですが以下のように調べていくといいと思います。
cat [対象ログファイル] | grep -i error
cat [対象ログファイル] | grep -i exception
とか、エラー系の文言でgrepすると引っかかると思います。もしソースのログ出力で特定のメッセージを出力することがわかっているなら、そのメッセージでgrepしてみるといいと思います。
もしくは以下のようにtailfコマンドをしつつ、エラーが発生しているサービスを動かして吐き出されたログを見ていく方法もおすすめです。
tailf [対象ログファイル]
エラーログが出力されていない場合
アクセスログを見てそもそもサーバまで到達しているか見る
コンテンツによってはエラーログが出力されていないものもあると思います。
本来であればエラーログは出るはずなのに出ていないと言うものもあるかもしれません。
その切り分けをしたいのであれば、アクセスログを見てサーバまで到達しているかを確認すると良いと思います。
もし到達していない場合はネットワーク側の問題かもしれません(今回WEBエンジニア向けなのでネットワーク問題は割愛します)エラーログを仕込む
そもそもソースでエラーログが出力されていない場合はエラーログを出力する処理を組み込んだ上でリリースして計測した方がいいかもしれません。かなりクリティカルな場合はインフラ側でアクセスを止めてメンテナンス画面にするなど、サービスを止める選択も関係者と相談する必要があります。
WEBコンテンツであればデベロッパーツールでエラーが出ていないか確認する
ログが出ていなければそもそもサーバ側が原因ではなく、
フロント側のJSなどでエラーが発生している可能性は大いにあります。GoogleChromeなどでOption+Command+iでデベロッパーツールを開き、「Console」から何かエラーが発生していないか確認するといいと思います。(以下のような感じ)
プログラム調査フェーズ
ここからはログである程度のエラーを特定したら、そのエラーを元にプログラムにどのような問題があるか調査していきます。
1.調査はサーバ上で行わない
まず、多くの人は対象ソースの調査をログ調査に引き続きサーバで行いがちな印象があります。
もし調査している人がlinuxコマンドをある程度体得しており、vimmerであれば止めないのですが個人的に対象ソースをSCPツール等でローカルに落としてきて、エディタや開発統合環境を使用して調査した方が良いです。ローカルで調査をするメリット
- 開発統合環境やエディタなどで関数ジャンプができる(プラグインを入れておきましょう)
- 全体grepを行った後に、対象ソースに飛びやすい(個人的におすすめのエディタはsublime)
2.各プログラム言語のデバッグ方法を理解する
テスト環境やローカル環境でも同事象が発生しているが、原因がわからない場合、
愚直ですが一つ一つの処理を追っていって原因を特定する必要があると思います。
そこで必要になってくる技術がプログラミングのデバッグです。言語にあったdebug方法を理解しておくことで、原因特定までの速度が格段に上がると思います。
Printデバッグを極力使わないように意識する
Printデバッグというのは
echo $test exitなどのプリントして変数の内容を確認しながらexitで処理を止めたりしてデバッグしていく方法です。
この方法は簡単かつ脳死しながらデバッグをすることができるのですが、各プログラミング言語にはよりわかりやすくデバッグする仕組みが存在するので、そちらを積極的に使用していくべきです。
例えば以下のような例になります。
言語 デバッグ方法 PHP debug_backtrace関数を使用したPrintデバッグをする、xdebugを使用する NodeJS debugコマンドを使用する Ruby byebugコマンドを使用する 調査結果報告フェーズ
調査をある程度することができたら、再度関係各署に報告を行います。
報告する内容は以下のような項目です。1.報告する内容
- 発生した事象
- 対象のサービス
- 発生期間
- 発生期間中のサービスへのアクセス数、障害影響件数
- 発生した原因
- 対応内容(暫定対応と恒久対応の提示)
- 再発防止策
など提示して報告すれば良いかと思います。
2.障害影響件数の抽出方法
この障害によってどの程度の影響があったか?は報告時に提示すべき項目かと思います。
その際に件数洗い出しで見る部分はアクセスログとエラーログです。① エラーログ件数ー実際その障害によりエラーが発生した件数はいくつか
② アクセスログ件数ー障害発生中にどの程度のアクセスが来ていたか基本的には①の件数を出せば良いのですが、②は画面崩れなどフロント側で発生していたエラーに対する件数を図る際に抽出すると良いと思います。
kibanaなどのログをGUI化するサービスを使用していない場合は、愚直にcutコマンドやxargsを駆使して対象エラー件数を抽出しましょう。
さいごに
いかがでしたでしょうか。
会社によって環境は様々なので、障害調査に関する正解をここで提示できないのですが、
自分の経験から他のところでも上記のような観点で調査をすれば、原因特定が早いのではないかと思いまとめさせていただきました。
ご参考いただければ幸いと思いつつ、もっと良い観点での調査方法があればコメントいただけますと幸いです。
- 投稿日:2020-07-26T11:30:44+09:00
サービスで障害が起きた時の調査方法まとめ(WEBエンジニア向け)
こんな人に読んで欲しい
- サーバーサイドエンジニアの人
- 障害・エラー発生時にあわわわわってなってしまう人
- 担当しているサービスをあんまり理解してないから調べ方わからん、と言う人
- どこから調べればよいかわからない人
何について話す?
WEBエンジニアとして、サービスを提供する開発をしていると
- リリース時
- 運用中
- 何もしていないのに突然
などエラーや障害が発生することがあるでしょう。
自分が所属するチームでは一つのコンテンツを長く管理しつつ、
他チーム離散など政治的事情で新しいコンテンツを受け入れる機会が多いです。
新しいコンテンツを受け入れた際に、突然来ます、エラーが、障害が・・・そんなエラーが発生した際にコンテンツへの理解がなくても原因を特定できた、
という機会がままあります。
(テーブル構成とかアーキテクトとか全く知らないけど、エラーを見つけて対応もできた、とか)そのような経験を通して、コンテンツへの理解があるからこそ調べられる調査と、
理解がなくても調べられる調査があると感じました。よくわからないコンテンツでもある程度の調査ができる勘所を紹介していきます。
調査能力を身に付けるための技術を身に付ける
コンテンツへの理解がなくても調べられる、と言いましたが技術的な知識は必要です。
AWSを扱っているのであれば「CloudWatchLogsってなんぞ?」
Linuxサーバで構築しているのであれば「apacheってなんぞ?log出力されているところってどこぞ?」
などと言われてしまうと調査ハードルは格段に上がると思います。以下の資格を取れる程度の知識を身に付けておくことをおすすめします。
- AWSソリューションアーキテクト(最低限クラウドプラクティショナー程度)
- LinuC(最低限Level1程度)
これらの知識がなくてもこれから提案する調査の勘所は参考になると思うので、
引き続き見てもらえますと幸いです。障害発生時フェーズ
1.緊急レベルを判断する
まずこのエラーは即時対応する必要があるのか、今週中に対応すればいいのか、
それとも放置してもいいものなのかを判断する必要があります。
会社などで決められているルールがあればそれを遵守すればいいですが、自分であれば以下のような基準で考えています。即時対応レベル
- サービス提供が不可能な状態が発生している(操作できない・不正データが登録される等)
- サービスは提供できているが利用者のユーザビリティを著しく落としている(画面の表示崩れ等)
- 簡易的な攻撃で不正動作ができてしまう状態(パラメータ操作やテキストエリアでSQLインジェクションができる等)
近日対応レベル
- 社内管理画面、運用をする際に弊害があるが、代替手段が取れる状態
- ユーザへのサービスに影響がないエラーログ等が出ている(Noticeエラーなど)
放置レベル
- ユーザーが使う頻度が極端に少ないコンテンツでサービス提供はできている状態でのエラー
- バッチなどで自動リカバリする仕組みが存在しているもの
2.上司・関係者に報告する
エラーレベルを判断したら関係者に報告しましょう。
上記で緊急対応レベルとわかったら即座に報告した方がいいです。
このような報告を怠ってエラー解消に尽力しがちですが、あとで何故すぐに報告しなかったんだ!!となってしまいます。障害発生を関係者が検知できていないと、関係者が行うべき対策が遅れてしまうことになります。他の人にお願いするでもいいので報告しましょう。また、自分起因のエラーが発生した時に
この事象を隠蔽することができないか
頭をよぎることがあると思います。
自分も何度も頭をよぎりますが、これは隠蔽しようとしてばれた時が最も怖いです。
自身に対する信頼が失墜します。誠実さを示して関係者に正直に報告しましょう。ログ調査フェーズ
ここからが調査についてです。
1.障害が発生しているサーバーを特定する(知っている場合は省略可)
心当たりがあるサーバに接続して以下の部分を調べます。
apache設定を見て、対象コンテンツのディレクトリ設定があれば対象サーバになります。
apache設定がそもそもどこにあるかも特定する必要がありますが、以下で動いている可能性が高いです。
OSとインストール方法 ディレクトリ CentOS,RHEL,Fedoraなど(パッケージ) /etc/httpd/conf/ SUSE,Debian,MacOSなど(パッケージ) /etc/apache2/ ソースインストール /usr/local/apache2/conf/ 以下のコマンドを入力すればある程度のapacheの場所がわかると思います。
$ ps aux | grep httpd
▼参考Qiita
Apacheの設定ファイル httpd.confの場所2.ログ出力の場所を特定する
対象ソースの中からログをどこに吐き出しているかソースをgrepしましょう。
ログ設定は以下のようなパターンが多いと思います。
- アプリケーション独自に定義している
- プログラミング言語のフレームワークの設定で定義している
- apacheでログ設定されている
- 各ミドルウェア・プログラミング言語のデフォルトのログ出力場所(/var/log/php等)
もしもソースにそのような設定が無かった場合、apacheを見たり会社独自のナレッジサイトがあるならそこから調査するのもいいと思います。
3.ログファイルから対象エラーログを特定する
対象のディレクトリ設定までわかれば、実際にログファイルの中で原因となるエラーを特定します。
もし対象ディレクトリ内に複数ファイルがあってどのファイルかわからない場合、障害が起きているサービスを動かしながら以下のコマンドを実行するといいです。
find [ログディレクトリ] -mmin -10
このコマンドを実行することで、対象サービスを動かした際にログが出力されたファイルが何なのか、ある程度特定することができます。
対象のファイルがわかったらファイル内をgrepしてみましょう。
grepの仕方は様々ですが以下のように調べていくといいと思います。
cat [対象ログファイル] | grep -i error
cat [対象ログファイル] | grep -i exception
とか、エラー系の文言でgrepすると引っかかると思います。もしソースのログ出力で特定のメッセージを出力することがわかっているなら、そのメッセージでgrepしてみるといいと思います。
もしくは以下のようにtailfコマンドをしつつ、エラーが発生しているサービスを動かして吐き出されたログを見ていく方法もおすすめです。
tailf [対象ログファイル]
エラーログが出力されていない場合
アクセスログを見てそもそもサーバまで到達しているか確認する
コンテンツによってはエラーログが出力されていないものもあると思います。
本来であればエラーログは出るはずなのに出ていないと言うものもあるかもしれません。
その切り分けをしたいのであれば、アクセスログを見てサーバまで到達しているかを確認すると良いと思います。
もし到達していない場合はネットワーク側の問題かもしれません(今回WEBエンジニア向けなのでネットワーク問題は割愛します)エラーログを仕込む
そもそもソースでエラーログが出力されていない場合はエラーログを出力する処理を組み込んだ上でリリースして計測した方がいいかもしれません。かなりクリティカルな場合はインフラ側でアクセスを止めてメンテナンス画面にするなど、サービスを止める選択も関係者と相談する必要があります。
WEBコンテンツであればデベロッパーツールでエラーが出ていないか確認する
ログが出ていなければそもそもサーバ側が原因ではなく、
フロント側のJSなどでエラーが発生している可能性は大いにあります。GoogleChromeなどでOption+Command+iでデベロッパーツールを開き、「Console」から何かエラーが発生していないか確認するといいと思います。(以下のような感じ)
プログラム調査フェーズ
ここからはログである程度のエラーを特定したら、そのエラーを元にプログラムにどのような問題があるか調査していきます。
1.調査はサーバ上で行わない
まず、多くの人は対象ソースの調査をログ調査に引き続きサーバで行いがちな印象があります。
もし調査している人がlinuxコマンドをある程度体得しており、vimmerであれば止めないのですが個人的に対象ソースをSCPツール等でローカルに落としてきて、エディタや開発統合環境を使用して調査した方が良いです。ローカルで調査をするメリット
- 開発統合環境やエディタなどで関数ジャンプができる(プラグインを入れておきましょう)
- 全体grepを行った後に、対象ソースに飛びやすい(個人的におすすめのエディタはsublime)
2.各プログラム言語のデバッグ方法を理解する
テスト環境やローカル環境でも同事象が発生しているが、原因がわからない場合、
愚直ですが一つ一つの処理を追っていって原因を特定する必要があると思います。
そこで必要になってくる技術がプログラミングのデバッグです。言語にあったdebug方法を理解しておくことで、原因特定までの速度が格段に上がると思います。
Printデバッグを極力使わないように意識する
Printデバッグというのは
echo $test exitなどのプリントして変数の内容を確認しながらexitで処理を止めたりしてデバッグしていく方法です。
この方法は簡単かつ脳死しながらデバッグをすることができるのですが、各プログラミング言語にはよりわかりやすくデバッグする仕組みが存在するので、そちらを積極的に使用していくべきです。
例えば以下のような例になります。
言語 デバッグ方法 PHP debug_backtrace関数を使用したPrintデバッグをする、xdebugを使用する NodeJS debugコマンドを使用する Ruby byebugコマンドを使用する 調査結果報告フェーズ
調査をある程度することができたら、再度関係各署に報告を行います。
報告する内容は以下のような項目です。1.報告する内容
- 発生した事象
- 対象のサービス
- 発生期間
- 発生期間中のサービスへのアクセス数、障害影響件数
- 発生した原因
- 対応内容(暫定対応と恒久対応の提示)
- 再発防止策
など提示して報告すれば良いかと思います。
2.障害影響件数の抽出方法
この障害によってどの程度の影響があったか?は報告時に提示すべき項目かと思います。
その際に件数洗い出しで見る部分はアクセスログとエラーログです。① エラーログ件数ー実際その障害によりエラーが発生した件数はいくつか
② アクセスログ件数ー障害発生中にどの程度のアクセスが来ていたか基本的には①の件数を出せば良いのですが、②は画面崩れなどフロント側で発生していたエラーに対する件数を図る際に抽出すると良いと思います。
kibanaなどのログをGUI化するサービスを使用していない場合は、愚直にcutコマンドやxargsを駆使して対象エラー件数を抽出しましょう。
さいごに
いかがでしたでしょうか。
会社によって環境は様々なので、障害調査に関する正解をここで提示できないのですが、
自分の経験から他のところでも上記のような観点で調査をすれば、原因特定が早いのではないかと思いまとめさせていただきました。
ご参考いただければ幸いと思いつつ、もっと良い観点での調査方法があればコメントいただけますと幸いです。
- 投稿日:2020-07-26T11:30:44+09:00
サービスで障害が起きた時の調査方法まとめ(バックエンドエンジニア向け)
こんな人に読んで欲しい
- サーバーサイドエンジニアの人
- 障害・エラー発生時にあわわわわってなってしまう人
- 担当しているサービスをあんまり理解してないから調べ方わからん、と言う人
- どこから調べればよいかわからない人
何について話す?
WEBエンジニアとして、サービスを提供する開発をしていると
- リリース時
- 運用中
- 何もしていないのに突然
などエラーや障害が発生することがあるでしょう。
自分が所属するチームでは一つのコンテンツを長く管理しつつ、
他チーム離散など政治的事情で新しいコンテンツを受け入れる機会が多いです。
新しいコンテンツを受け入れた際に、突然来ます、エラーが、障害が・・・そんなエラーが発生した際にコンテンツへの理解がなくても原因を特定できた、
という機会がままあります。
(テーブル構成とかアーキテクトとか全く知らないけど、エラーを見つけて対応もできた、とか)そのような経験を通して、コンテンツへの理解があるからこそ調べられる調査と、
理解がなくても調べられる調査があると感じました。よくわからないコンテンツでもある程度の調査ができる勘所を紹介していきます。
調査能力を身に付けるための技術を身に付ける
コンテンツへの理解がなくても調べられる、と言いましたが技術的な知識は必要です。
AWSを扱っているのであれば「CloudWatchLogsってなんぞ?」
Linuxサーバで構築しているのであれば「apacheってなんぞ?log出力されているところってどこぞ?」
などと言われてしまうと調査ハードルは格段に上がると思います。以下の資格を取れる程度の知識を身に付けておくことをおすすめします。
- AWSソリューションアーキテクト(最低限クラウドプラクティショナー程度)
- LinuC(最低限Level1程度)
これらの知識がなくてもこれから提案する調査の勘所は参考になると思うので、
引き続き見てもらえますと幸いです。障害発生時フェーズ
1.緊急レベルを判断する
まずこのエラーは即時対応する必要があるのか、今週中に対応すればいいのか、
それとも放置してもいいものなのかを判断する必要があります。
会社などで決められているルールがあればそれを遵守すればいいですが、自分であれば以下のような基準で考えています。即時対応レベル
- サービス提供が不可能な状態が発生している(操作できない・不正データが登録される等)
- サービスは提供できているが利用者のユーザビリティを著しく落としている(画面の表示崩れ等)
- 簡易的な攻撃で不正動作ができてしまう状態(パラメータ操作やテキストエリアでSQLインジェクションができる等)
近日対応レベル
- 社内管理画面、運用をする際に弊害があるが、代替手段が取れる状態
- ユーザへのサービスに影響がないエラーログ等が出ている(Noticeエラーなど)
放置レベル
- ユーザーが使う頻度が極端に少ないコンテンツでサービス提供はできている状態でのエラー
- バッチなどで自動リカバリする仕組みが存在しているもの
2.上司・関係者に報告する
エラーレベルを判断したら関係者に報告しましょう。
上記で緊急対応レベルとわかったら即座に報告した方がいいです。
このような報告を怠ってエラー解消に尽力しがちですが、あとで何故すぐに報告しなかったんだ!!となってしまいます。障害発生を関係者が検知できていないと、関係者が行うべき対策が遅れてしまうことになります。他の人にお願いするでもいいので報告しましょう。また、自分起因のエラーが発生した時に
この事象を隠蔽することができないか
頭をよぎることがあると思います。
自分も何度も頭をよぎりますが、これは隠蔽しようとしてばれた時が最も怖いです。
自身に対する信頼が失墜します。誠実さを示して関係者に正直に報告しましょう。ログ調査フェーズ
ここからが調査についてです。
1.障害が発生しているサーバーを特定する(知っている場合は省略可)
心当たりがあるサーバに接続して以下の部分を調べます。
apache設定を見て、対象コンテンツのディレクトリ設定があれば対象サーバになります。
apache設定がそもそもどこにあるかも特定する必要がありますが、以下で動いている可能性が高いです。
OSとインストール方法 ディレクトリ CentOS,RHEL,Fedoraなど(パッケージ) /etc/httpd/conf/ SUSE,Debian,MacOSなど(パッケージ) /etc/apache2/ ソースインストール /usr/local/apache2/conf/ 以下のコマンドを入力すればある程度のapacheの場所がわかると思います。
$ ps aux | grep httpd
▼参考Qiita
Apacheの設定ファイル httpd.confの場所2.ログ出力の場所を特定する
対象ソースの中からログをどこに吐き出しているかソースをgrepしましょう。
ログ設定は以下のようなパターンが多いと思います。
- アプリケーション独自に定義している
- プログラミング言語のフレームワークの設定で定義している
- apacheでログ設定されている
- 各ミドルウェア・プログラミング言語のデフォルトのログ出力場所(/var/log/php等)
もしもソースにそのような設定が無かった場合、apacheを見たり会社独自のナレッジサイトがあるならそこから調査するのもいいと思います。
3.ログファイルから対象エラーログを特定する
対象のディレクトリ設定までわかれば、実際にログファイルの中で原因となるエラーを特定します。
もし対象ディレクトリ内に複数ファイルがあってどのファイルかわからない場合、障害が起きているサービスを動かしながら以下のコマンドを実行するといいです。
find [ログディレクトリ] -mmin -10
このコマンドを実行することで、対象サービスを動かした際にログが出力されたファイルが何なのか、ある程度特定することができます。
対象のファイルがわかったらファイル内をgrepしてみましょう。
grepの仕方は様々ですが以下のように調べていくといいと思います。
cat [対象ログファイル] | grep -i error
cat [対象ログファイル] | grep -i exception
とか、エラー系の文言でgrepすると引っかかると思います。もしソースのログ出力で特定のメッセージを出力することがわかっているなら、そのメッセージでgrepしてみるといいと思います。
もしくは以下のようにtailfコマンドをしつつ、エラーが発生しているサービスを動かして吐き出されたログを見ていく方法もおすすめです。
tailf [対象ログファイル]
エラーログが出力されていない場合
アクセスログを見てそもそもサーバまで到達しているか確認する
コンテンツによってはエラーログが出力されていないものもあると思います。
本来であればエラーログは出るはずなのに出ていないと言うものもあるかもしれません。
その切り分けをしたいのであれば、アクセスログを見てサーバまで到達しているかを確認すると良いと思います。
もし到達していない場合はネットワーク側の問題かもしれません(今回WEBエンジニア向けなのでネットワーク問題は割愛します)エラーログを仕込む
そもそもソースでエラーログが出力されていない場合はエラーログを出力する処理を組み込んだ上でリリースして計測した方がいいかもしれません。かなりクリティカルな場合はインフラ側でアクセスを止めてメンテナンス画面にするなど、サービスを止める選択も関係者と相談する必要があります。
WEBコンテンツであればデベロッパーツールでエラーが出ていないか確認する
ログが出ていなければそもそもサーバ側が原因ではなく、
フロント側のJSなどでエラーが発生している可能性は大いにあります。GoogleChromeなどでOption+Command+iでデベロッパーツールを開き、「Console」から何かエラーが発生していないか確認するといいと思います。(以下のような感じ)
プログラム調査フェーズ
ここからはログである程度のエラーを特定したら、そのエラーを元にプログラムにどのような問題があるか調査していきます。
1.調査はサーバ上で行わない
まず、多くの人は対象ソースの調査をログ調査に引き続きサーバで行いがちな印象があります。
もし調査している人がlinuxコマンドをある程度体得しており、vimmerであれば止めないのですが個人的に対象ソースをSCPツール等でローカルに落としてきて、エディタや開発統合環境を使用して調査した方が良いです。ローカルで調査をするメリット
- 開発統合環境やエディタなどで関数ジャンプができる(プラグインを入れておきましょう)
- 全体grepを行った後に、対象ソースに飛びやすい(個人的におすすめのエディタはsublime)
2.各プログラム言語のデバッグ方法を理解する
テスト環境やローカル環境でも同事象が発生しているが、原因がわからない場合、
愚直ですが一つ一つの処理を追っていって原因を特定する必要があると思います。
そこで必要になってくる技術がプログラミングのデバッグです。言語にあったdebug方法を理解しておくことで、原因特定までの速度が格段に上がると思います。
Printデバッグを極力使わないように意識する
Printデバッグというのは
echo $test exitなどのプリントして変数の内容を確認しながらexitで処理を止めたりしてデバッグしていく方法です。
この方法は簡単かつ脳死しながらデバッグをすることができるのですが、各プログラミング言語にはよりわかりやすくデバッグする仕組みが存在するので、そちらを積極的に使用していくべきです。
例えば以下のような例になります。
言語 デバッグ方法 PHP debug_backtrace関数を使用したPrintデバッグをする、xdebugを使用する NodeJS debugコマンドを使用する Ruby byebugコマンドを使用する 調査結果報告フェーズ
調査をある程度することができたら、再度関係各署に報告を行います。
報告する内容は以下のような項目です。1.報告する内容
- 発生した事象
- 対象のサービス
- 発生期間
- 発生期間中のサービスへのアクセス数、障害影響件数
- 発生した原因
- 対応内容(暫定対応と恒久対応の提示)
- 再発防止策
など提示して報告すれば良いかと思います。
2.障害影響件数の抽出方法
この障害によってどの程度の影響があったか?は報告時に提示すべき項目かと思います。
その際に件数洗い出しで見る部分はアクセスログとエラーログです。① エラーログ件数ー実際その障害によりエラーが発生した件数はいくつか
② アクセスログ件数ー障害発生中にどの程度のアクセスが来ていたか基本的には①の件数を出せば良いのですが、②は画面崩れなどフロント側で発生していたエラーに対する件数を図る際に抽出すると良いと思います。
kibanaなどのログをGUI化するサービスを使用していない場合は、愚直にcutコマンドやxargsを駆使して対象エラー件数を抽出しましょう。
さいごに
いかがでしたでしょうか。
会社によって環境は様々なので、障害調査に関する正解をここで提示できないのですが、
自分の経験から他のところでも上記のような観点で調査をすれば、原因特定が早いのではないかと思いまとめさせていただきました。
ご参考いただければ幸いと思いつつ、もっと良い観点での調査方法があればコメントいただけますと幸いです。
- 投稿日:2020-07-26T01:31:45+09:00
EC-CUBE4でメールアドレス以外でログインする
今回は、EC-CUBEのフロント側 (ユーザがECサイトで買い物をする部分)のログインロジックを変更します。
想定
EC-CUBE4(Symfony3.4)を使用します。
ある程度EC-CUBE4やSymfonyに触れたことがある方向けとして、SymfonyとEC CUBE4の基礎は省略します。EC CUBE標準のフロントログインロジックは、登録されたメールアドレスを用いて行います。
ここでは、カラムlogid_id (ログインID)
を追加して、ログインIDでログインできるようにすることが目的です。DBの変更
Entityクラスの拡張
まずは、DBテーブルdtb_customerに
login_id
というカラムを追加しましょう。SymfonyのDBロジックのDoctrineでサポートされている、Entityクラスに対するTraitクラスを使用してカラム追加を行います。
Eccube\Entity\Customer
に対するCustomerTraitクラスを新規で作成しましょう。app/Customize/Entity/CustomerTrait.php<?php namespace Customize\Entity; use Doctrine\ORM\Mapping as ORM; use Eccube\Annotation as Eccube; use Symfony\Component\Validator\Constraints as Assert; /** * @Eccube\EntityExtension("Eccube\Entity\Customer") */ trait CustomerTrait { /** * @ORM\Column(name="login_id", type="string", length=255, nullable=true, unique=true) */ private $login_id; /** * Set login_id. * * @param string $login_id * * @return Customer */ public function setLoginId($login_id) { $this->login_id = $login_id; return $this; } /** * Get login_id. * * @return string */ public function getLoginId() { return $this->login_id; } }Entityクラスの記述をDBに反映する
作成後に、EC CUBEディレクトリの最も浅いところで下記のコマンドをそれぞれ実行しましょう。
Windows環境の方は、頭にphp
を付け足して実行してください。$ bin/console eccube:generate:proxies $ bin/console doctrine:schema:update --dump-sql --force1つ目のコマンドは、Entityのproxyファイルを生成するコマンドです。
app/proxy/entity
内に、対応するEntityのプロキシファイルが作成されれば成功です。
今回は、app\proxy\entity\src\Eccube\Entity\Customer.php
が作成されます。
ファイルの中身は、Eccube\Entity\Customer
の記述の中で、Customize\Entitiy\CustomerTrait
が呼び出されるものとなります。2つ目のコマンドでは、CustomerTraitにアノテーションで記載したSQLを実行する形となります。
下記の出力結果が表示されれば成功で、この場合dtb_customerにカラム追加が実行されます。
nullable=true
とすればDEFAULT NULL
としてカラム追加できます。
nullable=false
とすればNOT NULL
としてカラム追加できますが、login_id
追加時に値はまだNULLとなっているので、この場合では実行時にエラーとなります。下記が表示されれば成功です。
The following SQL statements will be executed: ALTER TABLE dtb_customer ADD login_id VARCHAR(255) DEFAULT NULL; CREATE UNIQUE INDEX UNIQ_8298BBE35CB2E05D ON dtb_customer (login_id); Updating database schema... 2 queries were executed [OK] Database schema updated successfully!ログインロジックの書き換え
security.yamlの変更
カラム追加ができたので次に移りましょう。
app\config\eccube\packages\security.yaml
を書き換えて、ログインロジックに用いるクラスを変更します。providersでは、ECサイトの管理画面側とフロント画面側のログインロジックで使用しているクラスが記述されています。
ここでは、フロント側で後ほど使用するクラスを呼び出すよう追記しましょう。app/config/eccube/packages/security.yaml L1~L17に追加providers: # https://symfony.com/doc/current/security.html#b-configuring-how-users-are-loaded # In this example, users are stored via Doctrine in the database # To see the users at src/App/DataFixtures/ORM/LoadFixtures.php # To load users from somewhere else: https://symfony.com/doc/current/security/custom_provider.html member_provider: id: Eccube\Security\Core\User\MemberProvider customer_provider: # id: Eccube\Security\Core\User\CustomerProvider # 本来書いてあったロジック id: Customize\Security\Core\User\CustomerProviderCustomized # 新たに呼び出すクラスを記述次に、ログイン時に
login_id
を使用することを明示化します。
動作上はlogin_email
でも支障ないのですが、書き換えましょう。app/config/eccube/packages/security.yaml L49~L59に追加form_login: check_path: mypage_login login_path: mypage_login csrf_token_generator: security.csrf.token_manager default_target_path: homepage # username_parameter: 'login_email' login_emailは使わずに、login_idを使用する username_parameter: 'login_id' password_parameter: 'login_pass' use_forward: false success_handler: eccube.security.success_handler failure_handler: eccube.security.failure_handlerログインロジッククラスの拡張
次に、フロント側で使用するログインロジッククラスを作成します。
app\Customize\Security\Core\User\CustomerProviderCustomized.php<?php namespace Customize\Security\Core\User; use Eccube\Entity\Customer; use Eccube\Entity\Master\CustomerStatus; use Eccube\Repository\CustomerRepository; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; class CustomerProviderCustomized implements UserProviderInterface { /** * @var CustomerRepository */ protected $customerRepository; public function __construct(CustomerRepository $customerRepository) { $this->customerRepository = $customerRepository; } /** * Loads the user for the given username. * * This method must throw UsernameNotFoundException if the user is not * found. * * @param string $username The username * * @return UserInterface * * @throws UsernameNotFoundException if the user is not found */ public function loadUserByUsername($username) { $Customer = $this->customerRepository->findOneBy([ 'login_id' => $username, 'Status' => CustomerStatus::REGULAR, ]); if (null === $Customer) { throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username)); } return $Customer; } /** * Refreshes the user. * * It is up to the implementation to decide if the user data should be * totally reloaded (e.g. from the database), or if the UserInterface * object can just be merged into some internal array of users / identity * map. * * @return UserInterface * * @throws UnsupportedUserException if the user is not supported */ public function refreshUser(UserInterface $user) { if (!$user instanceof Customer) { throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user))); } return $this->loadUserByUsername($user->getLoginId()); } /** * Whether this provider supports the given user class. * * @param string $class * * @return bool */ public function supportsClass($class) { return Customer::class === $class; } }ここでは、
Eccube\Security\Core\User\CustomerProvider
を踏襲しつつ、メソッド2つ(loadUserByUsername
とrefreshUser
)
を変更しています。
CustomerProvider
を踏襲・オーバーライドして記述を少なくすることも可能ですが、今回は前述のようにUserProviderInterface
をimplementsで拡張する方式となっております。メソッド
loadUserByUsername
は、主にログイン時に使用するメソッドになります。
引数で受け取る$username
に対し、dtb_customer.login_id
を検索するように書き換えました。メソッド
refreshUser
は、画面ロード時等に最新のユーザ情報をDBから取得します。
EC CUBEのデフォルトですと$this->loadUserByUsername($user->getUsername())
でメールアドレスを引数とするので、こちらもログインIDを使用するように書き換えました。ログインテンプレートの書き換え
FormTypeの拡張
EC CUBEのMYページのログイン画面のFormTypeを書き換えます。
security.yaml
を変更したのと同様に、login_email
を削除してlogin_id
を追加します。app/Customize/Form/Extension/Front/CustomerLoginTypeExtension.php<?php /* * This file is part of EC-CUBE * * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved. * * http://www.ec-cube.co.jp/ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Customize\Form\Extension\Front; use Eccube\Common\EccubeConfig; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Form\AbstractTypeExtension; use Eccube\Form\Type\Front\CustomerLoginType; class CustomerLoginTypeExtension extends AbstractTypeExtension { /** * @var EccubeConfig */ protected $eccubeConfig; /** * @var AuthenticationUtils */ protected $authenticationUtils; public function __construct(AuthenticationUtils $authenticationUtils, EccubeConfig $eccubeConfig) { $this->authenticationUtils = $authenticationUtils; $this->eccubeConfig = $eccubeConfig; } /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->remove('login_email') ->add('login_id', TextType::class, [ 'attr' => [ 'max_length' => $this->eccubeConfig['eccube_stext_len'], ], 'constraints' => [ new Assert\NotBlank(), ], 'data' => $this->authenticationUtils->getLastUsername(), ]); } /** * {@inheritdoc} */ public function getExtendedType() { return CustomerLoginType::class; } }最大文字数と必須入力を指定し、
'data' => $this->authenticationUtils->getLastUsername()
でログインエラー時に入力されたログインIDを返すようにしています。Twigテンプレートの拡張
次にMYページのログイン画面のTwigテンプレートを拡張しましょう。
src/Eccube/Resource/template/default/Mypage/login.twig
をapp/template/default/Mypage/
以下にコピーすることで、app
以下のものが反映されるようになります。app/template/default/Mypage/login.twig{% block main %} <div class="ec-role"> <div class="ec-pageHeader"> <h1>{{ 'common.login'|trans }}</h1> </div> <div class="ec-off2Grid"> <div class="ec-off2Grid__cell"> <form name="login_mypage" id="login_mypage" method="post" action="{{ url('mypage_login') }}"> {% if app.session.flashBag.has('eccube.login.target.path') %} {% for targetPath in app.session.flashBag.get('eccube.login.target.path') %} <input type="hidden" name="_target_path" value="{{ targetPath }}" /> {% endfor %} {% endif %} <div class="ec-login"> <div class="ec-login__icon"> <div class="ec-icon"><img src="{{ asset('assets/icon/user.svg') }}" alt=""></div> </div> <div class="ec-login__input"> <div class="ec-input"> {{ form_widget(form.login_id, {'attr': {'style' : 'ime-mode: disabled;', 'placeholder' : 'ログインID', 'autofocus': true}}) }} {# login_emailをコメントアウトする #} {# {{ form_widget(form.login_email, {'attr': {'style' : 'ime-mode: disabled;', 'placeholder' : 'common.mail_address', 'autofocus': true}}) }} #} {{ form_widget(form.login_pass, {'attr': {'placeholder' : 'common.password' }}) }} </div>最後に
ログインIDの発行については、要件等に応じて別途下記のような実装が必要になるかと思います。
- EC会員登録時に、会員自らがユニークなログインIDの入力を可能とする。
- EC会員登録時に、ユニークなログインIDを生成する
- etc..
何か間違った記述がある場合はコメント等にてご指摘等お願いします。
参考文献
[Symfony公式 ver3.4]How to Build a Traditional Login Form
https://symfony.com/doc/3.4/security/form_login_setup.htmlSymfonyでGuardとEntityを使った認証 (@asaokamei様) (Quita)
https://qiita.com/asaokamei/items/5122e398e4cc17c84429[EC CUBE公式]Entityのカスタマイズ
https://doc4.ec-cube.net/customize_entity
- 投稿日:2020-07-26T01:13:27+09:00
aタグで作ったボタンでPOST送信する
はじめに
PHPのフォームで値を送信する際、aタグで作ったボタンでも送信する方法です。
自分が実装する際に困った箇所もまとめます。なぜaタグで送信する必要があったか
・inputタグ(type="submit")ではデザイン通りに実装できなかったため。
・formの外のボタンはaタグで実装されており、サイト・システム全体でボタンのスタイルや挙動を統一したかったため。ソースコード
名前とメールアドレスを送信するフォームを例に。
index.phpでフォームの値を送信してフォーム上に値を表示させてみます。form.php<?php if ( isset( $_POST[ "name" ] ) ) { $name = htmlspecialchars( $_POST[ "name" ], ENT_QUOTES, "utf-8" ); echo $name; } if ( isset( $_POST[ "email" ] ) ) { $email = htmlspecialchars( $_POST[ "email" ], ENT_QUOTES, "utf-8" ); echo $email; } ?> <!-- input[type="submit"]で送信--> <form method="POST" name="register" action="index.php"> <input type="text" name="name" value=""> <input type="text" name="mail" value=""> <input type="submit" value="送信する"> </form> <!-- aタグで送信する--> <form method="POST" name="register" action="index.php"> <input type="text" name="name" value=""> <input type="text" name="mail" value=""> <a href="#" onclick="document.register.submit();">登録する</a> </form>説明
・aタグにonclickでformのnameを指定してsubmitイベントを実行させる。
注意点
・同ファイルで同時に表示されているformのnameが同じの場合、formのが正しく動いてくれない。(値が送信できない)