- 投稿日:2019-11-12T22:58:57+09:00
【サルが書く】Laravelのpassportでの認証するテーブルをカスタマイズ方法
わきゃ〜〜
LaravelのPassportを使い、OAuth2.0のユーザー認証を行いました。
そこで、標準のemail,passwordのみの認証ではない認証方法を取りたかったので、ちょっと調べてみました。実行環境
Laravel 5.5
Laravel Passport 4.0
PHP 7.xLaravel Passportは導入した状態でのお話になります。
認証するカラムを変更したい
といっても、かなり簡単にカスタマイズは簡単です。
認証するテーブルのModelに下記のコードを追加するだけです。usernameを認証するカラムを変更する場合
Model/User.phppublic function findForPassport($username) { // 認証カラム名が name の場合 return $this->where('name', $username)->first(); }これは実際に認証を行っている、Laravel Passportの
Laravel/Passport/Bridge/UserRepository.php
にあるgetUserEntityByUserCredentials()
というメソッドがあります。
そのメソッドにModelがfindForPassportというメソッドを持っていれば、メソッドを上書きするような処理があるためです。認証する条件を変更する
whereをつなげて認証する条件を追加することもできます。
whereで表現できるものであれば、カスタマイズ可能だと思います。Model/User.phppublic function findForPassport($username) { // nameとrole_idの2つの条件で認証を行う return $this->where('name', $username)->where('role_id', $role_id)->first(); }参考文献
- 投稿日:2019-11-12T22:49:26+09:00
LAMP環境構築手順: 仮想環境
LAMP構築: Linux仮想環境作成 by Virtual Box
よくLAMPの仮想環境作成するので手順をまとめてみました。
多分Vagrantとか使ったらいいんでしょうけど、いろんな仮想環境を構築するのでなれもふくめて、まとめていきます。手順通りにやれば1時間以内には終わると思います。
環境依存かもしれないのでisoファイルは似た感じのを使ったほうが苦労は少ないかと。準備
VirtualBoxのインストール
isoファイルのダウンロード
今回使用するisoはCentOsのMinimalを使用しています。仮想環境の作成
VirtualMachineを作成
- タイプ: Linux
- Version: RedHat(64bit)
それ以外の部分はOkOkOKで大丈夫です。
VirtualMachineの設定
- Network: ブリッジアダプター
起動
マウスで操作できなければ、気合でTabを駆使して頑張ってください。
1. ディスクの選択: CentOS-7-x86_64-Minimal-1810.iso
2. INSTALLATION DESTINATION を選択して、Done で戻ると BeginInstallation が選択できる。
3. root password を設定する。
4. しばらくまつ。
5. Reboot ボタンが押せるようになると思うので、それで再起動しましょうLinuxの設定
root ユーザでloginしてください。
Network接続
nmtuiを起動します。
nmtui
- Edit a connection
- enp0s3(一個しかないはず) > Edit
- Automatically connect をTrueにする(SpaceでOn/Off切り替えができる)
- Ok押して、Quitする。
YumのUpdate
yumのUpdateが行えたら、Networkに接続できてます。
yum -y update
Centさんが頑張ってくれます。長いので気長に待ちましょう...
とりあえずこれで、仮想環境の作成ができました。
次は、Apacheのインストールにまいりましょう。LAMP構築: ApacheのInstallataion
ではではApacheをインストールしていきたいと思います。
Apacheのインストールは簡単ですが、SelinuxやFirewallなどに躓くことがあります。
できるだけ楽してやっていきましょうApacheのインストール
# apacheのインストール yum -y install httpd # apache serverの起動を永続化 systemctl enable httpdこれだけです。簡単ですね。
つぎが最初のころにはまる問題点だと思います。Security関係
初期状態だとApacheServerを起動してもアクセスできないので、
設定を行っていきます。firewallにhttpserviceを追加
ファイヤ―ウォールさんが http の接続をはじいちゃいますので、
http をはじかないように設定しましょう。firewall-cmd --zone=public --permanent --add-service httpSellinuxの無効化
本番環境では無効にしてはいけないのですが開発環境なので、無効にするのが楽なので無効にしてしまいます。
vi で /etc/selinux/config 編集しましょう。
viはLinuxのエディターです。
i を押すと編集モードに、esc でコマンドモードになります。
編集が終わったらコマンドモードで :wp と入力し保存しましょうvi /etc/selinux/config/etc/selinux/configSELINUX=disabledでは設定の反映のため再起動します。
rebootChromeで表示
ではChromeでちゃんとアクセスできるか確認しましょう。
Statusの確認
再起動がおわればそれぞれのStatusを確認しましょう
getenforce service httpd status
- getenforce
- Disabled と表示されればOKです
- service httpd status
- Active: active (running) と表示があればOKです。
サイトにアクセス
では実際にWebサイトにアクセスしましょう。
まず ipaddress を確認します。
ip -a
enp0s3: のsectionにある、これっぽいなーってやつがadressです。(最後が255とかやないやつ)
例: 192.168.1.100そのアドレスをChromeのurlに打ち込みましょう。
Testing 123...
と表示されれば接続できてますねー。
表示内容の編集
apacheの初期設定では、/var/html/ がserverのルートDirectoryです。
そこの index.html を最初に参照します。
実際にindex.htmlを作成して、サイトの表示が変わるか試してみましょう。echo "Hello World" > /var/www/html/index.htmlこれで /var/www/html/index.html に Hello World と書き込まれました。
ChromeをReloadして
Hello World
と表示されれば成功です。
以上でApacheのInstallationが完了しました。
次はPHPのInstallationに進みましょう!
LAMP構築: PhpのInstallation
ではではPHP7をインストールしていきたいと思います。
Php7のインストール
デフォルトではyumにphp7が入ってないみたいなので、
yumでインストールできるようにRepositoryを追加してから。
PhpとPhpの基本Libralyをインストールしていきます。# install php7 repo at yum repository. yum -y install http://rpms.famillecollet.com/enterprise/remi-release-7.rpm #install php7 yum install -y --enablerepo=remi,remi-php74 php php-gd php-pdo php-mbstring php-xmlインストールができたらApacheを再起動します。
service httpd restart動作確認
では、実際にPhpが動くか確認しましょう。
/var/www/html/ に php_info.php を追加してChromeで表示してみます。echo "<?php phpinfo();" > /var/www/html/php_info.phpではChromeでアクセスしてみましょう。
URLに [ipadress]/php_info.php でアクセスできます。
例: 192.168.1.100/php_info.phpPhpの情報がずらーと表示されればOKです。
これでphpもインストールすることができました!
次はmysqlですね。
LAMP構築: MySqlのInstallation
ではではMySql8をインストールしていきたいと思います。
個人的には最難関。間違ってMysql5をインストールしてからMysql8に戻すとかする時に、ちょっとつかれる...
ここらへんで VirtualBoxのSnapshot に保存とかしとくといつでも戻れるのでチャレンジしやすいかもです。MySql8のインストール
デフォルトではyumにmysqlが入ってないみたいなので、
yumでインストールできるようにRepositoryを追加してから,
MySql8をインストールしていきます。最新のMySqlのRepositoryを確認しましょう
/mysql80-community-release-el7-3.noarch.rpm の部分を最新のに合わせてください。# add mysql-community-server repo at yum repository yum -y install http://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm # install mysql server yum -y install mysql-community-serverCentさんがめちゃくちゃ頑張ってくれます。
長いので、猫と戯れるなりして気長に待ちましょう...MySqlの起動
では実際に起動しましょう
# mysqlの起動永続化 systemctl enable mysqld # mysqlの起動 systemctl start mysqld # statusの確認 systemctl status mysqldこれで Active: active (running) になっていればOKです。
MySqlの初期設定
初期パスワード
まずrootユーザーが作成されているので、rootユーザーの初期パスワードを入手しましょう
cat /var/log/mysqld.log | grep rootA temporary password is generated for root@localhost: xxxxxxxxx
xxxxxxxxxx の部分が初期パスワードです。基本はこれで出てくるはずなんですがもしなんか出てこないとなったらここを参照してください
参照先の my.cnf は /etc/my.cnf のことです。自動設定
では初期設定を行います。
mysql_secure_installation を使用して設定します。mysql_secure_installation
- パスワードを聞かれるのでゲットした初期パスワードを入力しましょう。
- 新しいパスワードを設定してください。
- それ以外はすべてEnterを押していけばOKです。
パスワード認証方式の変更
Mysql8のパスワード認証方式にPHPが対応していないので対応している認証方式に変更します。
/etc/my.cnf[mysqld] default-authentication-plugin=mysql_native_passwordすべての設定が完了したらMySqlを再起動しましょう
systemctl restart mysqldMySqlにアクセス
ではrootユーザでアクセスしてみましょう。
下記のコマンドを実行してパスワードを聞かれるの入力してください。mysql -u root -p # アクセスできるとコマンドラインの最初に mysql>と表示される # databaseの一覧を表示 mysql>show databases; # できたらquitしましょう mysql> quitこれでMysqlのInstallationが完了しました。
まとめ
以上LAMPの環境構築でした。
LAMPの環境構築は始めてやるとはまりまくりますが、黒い画面になれるためにも一度一からやってみると勉強になりますよね。仮想環境はめちゃくちゃ便利なのでぜひ活用していきたいと思います。
なんか変になったら消せばいいし、SnapShot機能もあるので戻せますし。次は復習がてらにPhpの自作WebFrameworkを作ろかと思います。
ではその時に会いましょう。
- 投稿日:2019-11-12T21:38:53+09:00
Moodle 3.7 マニュアル - 自動アップデートの展開
内容
1 アップデートの展開を有効にする
2 アップデートの展開を無効にする
3 起こり得る問題
3.1 インストールボタンが表示されない
3.2 プラグインファイルに書き込みできない
3.3 パッケージをダウンロードできない
4 エラーと例外
4.1 パッケージをダウンロードできない(download_file_exception))1 アップデートの展開を有効にする
この機能を使うには、Available update notifications(https://docs.moodle.org/37/en/Available_update_notifications) が利用可能である必要があります。機能は、web サーバがプラグインがアップデートされるフォルダへ書き込み権限があれば、デフォルトで有効化されます。
2 アップデートの展開を無効にする
ある数少ない環境(多数のローカルな変更がある完全にマージされたサーバや、独自のアップデートの展開をしているサイト - Git チェックアウト を経由して - のように)においては、自動アップデートの展開を許可しないことが望ましいです。この機能は、config.php ファイルに次のコードを追加することにより、完全に無効にされます。
// Use the following flag to completely disable the installation of plugins // (new plugins, available updates and missing dependencies) and related // features (such as cancelling the plugin installation or upgrade) via the // server administration web interface. $CFG->disableupdateautodeploy = true;3 起こり得る問題
3.1 インストールボタンが表示されない
もしアップデートの展開機能が有効にされない(あるいは、config.php により無効にされる)のなら、アップデートするためのボタンは表示されません。この機能が有効にされていると、展開が働くことを確かめるために、ある事前チェックを実行する、入手可能なアップデートのリスト、のページが表示されます。
3.2 プラグインファイルに書き込みできない
展開の最中、Moodle は、全フォルダをプラグインの新しいバージョンのコードで置き換えます。web サーバプロセスは、フォルダとその全ての内容に書き込み権限がなければいけません。これを実現するには、あなたの web サーバの設定や個人の好みにより、いくつかの方法があります。プラグインフォルダの正確な場所はプラグインのタイプに依存します。全場所のリストは、Plugins developer docs を参照してください。
例:あなたの web サーバが Apache、Linux サーバで、ユーザは www-data です。あなたの Moodle は /var/www/vhosts/moodle/htdocs にインストルされています。あなたは、activity modules に書き込み権限を与えて、アップデートします。
# cd /var/www/vhosts/moodle/htdocs # chown -R www-data mod # chmod -R u+w modプラグインインストール を参照してください。
3.3 パッケージをダウンロードできない
http://moodle.org/plugins が上がっているのを確認してください。もしこのサイトがダウンしているなら、あなたの Moodle サイトは、そこから ZIP ファイルを取ってこられません。http://moodle.org/plugins がアップすることを確認し、展開の手順を繰り返してください。
SSL 証明書の有効性でも問題が生じ得ます。 SSL certificate for moodle.org を確認してください。
4 エラーと例外
次の章では、起こり得るエラーとその対処方法について記述しています。
4.1 パッケージをダウンロードできない(download_file_exception)
Check the bottom of the mdeploy.log file. It will probably contain a line starting with "cURL error" followed by the error number and the cURL error description.
cURL error 7 couldn't connect to host
Make sure that the site http://download.moodle.org is up and running at the moment. If it is down, your site can't call the web service to fetch the available updates info. Wait for http://download.moodle.org to be up again then re-check.cURL error 60 (SSL certificate problem)
This suggests problems with the validation of the SSL certificate of the remote (moodle.org) site. See SSL certificate for moodle.org for more info.カテゴリ:インストール
- 投稿日:2019-11-12T18:54:50+09:00
IBM CloudのDelivery PipelineでPHPUnitを実行する方法
CI/CDを考える上で、外せないのがテストの自動化ですよね。
自身が更新したアプリケーションを、デプロイする前に最低限のチェックをかけたい、というのは誰もが抱く思いでしょう。
IBM Cloud
でも、CI/CDを一瞬で構築できるの仕組みがあり非常に重宝します。
今回の記事では、私がよく使っているPHPUnit
を使って自動でテストする方法をご紹介します。自動テストを実現する2つの方法
外部CI/CDサービスを使って実現する
Github
とTravis CI
等の外部CI/CDツールを使って、Pull Request
がマージされる前にテストを実行して確認しよう!というものです。
実は既に、PHP アプリケーションを自動的にテストして IBM Cloud にデプロイするという記事があります。割とオーソドックスなのはこちらの方法かと思いますので、ぜひ、ご一読ください。Delivery Pipeline内で直接テスト環境を用意してテストを実行する
Delivery Pipeline
では、自分でシェルを書くことができます。テストステップを追加して、中に下記のシェルを書くだけで、phpunitをDeployする前に実行することができます。
何らかの事情により、外部サービスが使えない時などに、役にたつかもしれないテクニックですね!
もちろん、他の言語でもシェルの中でプログラムの実行環境をセットアップすればです。
PHPUnit環境構築シェル
#!/bin/bash #まずはPHPと関連するextentionをinstall sudo apt install software-properties-common -y sudo add-apt-repository ppa:ondrej/php sudo apt-get update sudo apt-get install -y php7.2 php7.2-fpm php7.2-mysql php7.2-mbstring php7.2-zip php7.2-xml #composerをinstall curl -sS https://getcomposer.org/installer | php #必要なパッケージをinstall php composer.phar install #ここでテストを起動します php vendor/phpunit/phpunit/phpunit tests/StackTest.phpテストに成功した場合
テストに失敗した場合
上記シェルの実行Log(抜粋)
きちんと
PHP Unit
が実行されている事がわかります。There was 1 failure: 1) StackTest::testPushAndPop Failed asserting that 0 is identical to 1. /home/pipeline/c7703b7e-15e2-47fd-ba16-ef57ecd30f0f/tests/StackTest.php:9 FAILURES! Tests: 1, Assertions: 1, Failures: 1. WARNING: No file found for the pattern: tests/TEST-*.xml . Not uploading test results. WARNING: No file found for the pattern: target/site/jacoco/jacoco.xml . Not uploading test results. Finished: FAILED
- 投稿日:2019-11-12T17:29:25+09:00
Doctrineでちょっと複雑なSQLを発行する
Doctrineでちょっと複雑なSQLを発行しようとすると躓くことが多いです(個人的に)。
ハマったポイントをいくつかまとめてみたいと思います。SQLで使える関数が使えない
もちろん特定のRDBMSに依存していないライブラリなので、それぞれのRDBMSで使える関数は使えません。
でもDoctrineで使える関数もいくつかあります。
以下のページにまとまっています。DQL Functions
https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html#dql-functions上記のリンクは公式のリファレンスですが、
コードサンプルがないのが悲しいところです。
例えばDATE_ADDを使いたいときはDATE_ADD(date, days, unit) - Add the number of days to a given date. (Supported units are DAY, MONTH)
この一文を見て使い方を予測します。
今から1ヶ月後はDATE_ADD(CURRENT_DATE(), 1, 'MONTH')今から1日前は
DATE_ADD(CURRENT_DATE(), '-1', 'DAY')※-1にはシングルクォートをつけないと怒られます。
で、Supported units are DAY, MONTHとあるようにYEARには対応していません。
もちろんエラーです。よく使うものはだいたいあるかなという感じですが、個人的にはMySQLにあるDATE_FORMAT(OracleならTO_CHAR)がないのが辛いです。
DBに登録されている日付から日だけ取得するとか、
時間を切り捨てて日付のみの比較をするようなことができないからです。
次はDATE_FORMATについて書いてみます。DATE_FORMATが使えない
個人的には結構辛いものがあるのですが、
なにかうまい代替方法はあるものでしょうか・・・?
大抵は渡す引数を工夫したりしますが、どうしてもDATE_FORMATを使いたいときはDQL関数拡張という機能を使います。ドキュメントはこちら
DQL User Defined Functions
https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/cookbook/dql-user-defined-functions.htmlドキュメントにはサンプルコードがあります。
サンプルコードを見れば関数の追加自体は簡単そうに見えますが、
関数の中身を書くのはちょっと手間そうです。そんなとき、いろいろな関数をまとめて実装してくださっているコードがgithub上で提供されています(ありがたい)。
DoctrineExtensions
https://github.com/beberlei/DoctrineExtensionsこちらを導入することで、DATE_FORMATも使えるようになります。
手順はサンプルコードにあるので割愛。FROM句でサブクエリを使いたい
Doctrineではサポートしていません。
過去に要望もあったようですが、却下されたようです。DDC-2793: Subquery into FROM
https://github.com/doctrine/orm/issues/3542FROM句でサブクエリを使うようなときはネイティブなクエリを使ってとのことです。
ネイティブなクエリを発行する
本当に複雑なSQLを発行するときは結局はここにたどり着きます。
以下のような感じで発行できます。$sql = <<<___SQL SELECT * FROM order WHERE order_id = :order_id ___SQL; $params = ['order_id' => $order_id]; $em = EntityManager::create($dbParams, $config); // EntityManagerのgetはそれぞれのやり方で $stmt = $em->getConnection()->prepare($sql); $stmt->execute($params); $result = $stmt->fetchAll();ネイティブなクエリを発行したもののEntityと紐付けるのがめんどう
上記のサンプルだと、orderテーブルの中身を全部返しているのでOrderエンティティを返してほしいところです。
そんなときはResultSetMappingBuilderを使います。$sql = <<<___SQL SELECT * FROM order o WHERE order_id = :order_id ___SQL; $rsm = new ResultSetMappingBuilder($em); $rsm->addRootEntityFromClassMetadata('Path\to\Order\Entity', 'o'); $query = $em->createNativeQuery($sql, $rsm); $query->setParameter('order_id', $order_id); $orders = $query->getResult();おわりに
まだまだDoctrineを使いこなしていませんが、いろいろ調べていきたいと思います!
- 投稿日:2019-11-12T15:42:13+09:00
php 配列 キーを任意の値にしたい
$assignedUsersList = $this->repository->getList()->unique('assigned_user_id'); $assignedUsersName = []; foreach ($assignedUsersList as $assignedUser) { $assignedUsersName[$assignedUser['assigned_user_id']]<-ここ = $assignedUser->assigned_user->name; } // dd($assignedUsersName); $comicTrialPager = $this->repository->getPager($requestParams);
- 投稿日:2019-11-12T15:18:15+09:00
【API】ホットペッパーAPIで近くのお店を返すLinebotを作ってみた。【PHP】【初学者向け】
前書き
とても苦労したlinebot作成についてアウトプットしてより理解を深めようと思い、初めてqiitaを書きます。おそらくお見苦しい点ばかりでしょうが、先輩方はぜひ暖かい眼差しで見守っていてください。
linebotとは
引用
LINE BOTとは、一言でいえばコミュニケーションアプリLINEで動く、チャットボットだ。チャットボットとは?方は、当サイト記事のチャットボットとはを参考にして欲しいが、チャットアプリ上で動くロボットの事だ。
リンク
使用したライブラリ
今回はjyukiさんのライブラリ使用させていただいて、サクッと仕組みを作っていきます。
必要なもの
・サーバー(ssl接続可能な)
・Line Developersアカウント
・ホットペッパーAPIアカウントの作成今回はherokuというサービスを使ってlinebotを作成していく方法を記載していきます。
Line Developersアカウントの登録や設定はすでにqiitaにいくつも記事があるので探して実装してみてください。
ホットペッパーAPIの利用登録をして、きちんとAPIKeyをメモしておきましょう(必ず必要です)。
herokuでアプリケーション作成
herokuの公式サイトでアカウントを作成し、macOSならばherokuをhomebrewなどでインストールしておきましょう。
ライブラリをgit cloneしたディレクトリに移動し、以下のコマンドを入力し、ひとまずライブラリの中身をherokuにデプロイしてみましょう。herokuheroku login heroku create [アプリ名] git init git add . git commit -m "first commit" git push heroku masterこのアプリ名はlinedevepolerのwebhookURLの
https://[アプリ名].herokuapp.com/
この部分にも使います!APIの使用
http://webservice.recruit.co.jp/hotpepper/gourmet/v1/?key='API_KEY&lat=35.696274&lng=139.74149
上記のURLの[key=]以下に各々のAPIKEYをぶっこんでURLにアクセスしてみてください。XML形式で靖国神社付近のお店が返り値として取得できていますね。
今回のlinebotは利用者が位置情報を送るとこの店名と店のURLを返すものとなっています。
お店は最大5店まで送ることができます。(地元で試してみたら1店舗も出ませんでした)こちらに様々なオプションが掲載されているので、いろんな情報を取得してみるのも面白いです。
ジャンルを絞って店舗を検索するように改良してみたりするのもいいかもしれませんね!linebotでの活用
まず、linebotのリファレンスを確認します。
位置情報が送られてきた時に、送られてくる情報は以下の通りです。location{ "type": "location", "title": "my location", "address": "〒150-0002 東京都渋谷区渋谷2丁目21−1", "latitude": 35.65910807942215, "longitude": 139.70372892916203 }必要なのは"latitude"と"longitude"になりますね。
では次にライブラリの位置情報が送られてきた時行なっている処理のところを探してみましょう。locationif ($messeage_type == "location") {ここですね。
location$locaation = $bot->get_location(); $lat = $locaation['latitude']; $log = $locaation['longitude'];ここで位置情報を取得し、緯度経度の値を変数にぶち込みます。
そして今回APIの情報を取得する時に使うのはcurlです。(API情報取得にはfile_get_contentsではなく、curlの方が良いらしいです)
curl$url = 'http://webservice.recruit.co.jp/hotpepper/gourmet/v1/?key=[APIKEY]&lat=$lat&lng=$long'; //セッション初期化 $ch = curl_init(); //オプション url_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); //値を取得する。 $res = curl_exec($ch); $json = json_decode($res, true);このような感じで外部URLの情報を取得し、連想配列として値を受け取ります。
debugfile_put_contents('任意のtxtファイル', print_r($json), true);のような感じでデバッグを行い、変数に配列が入っているか、どのような構成で入っているかを確認してみましょう。
https://[アプリ名].herokuapp.com/[ここに適当に作った任意のtxtファイル]
herokuのURLにアクセスすることで確認できます。
name$nameOne = $json['results']['shop']['0']['name'] . $json['results']['shop']['0']['urls']['pc'];店名と予約できるURLを使いやすいように変数に入れてあげます。返したいお店の数だけ変数を用意してあげてください。
send//テキストの送信フロー $bot->add_text_builder($nameOne);ここまでできたらデプロイして、実際に位置情報を送って値が返ってくるか確認してみてください!
参考にさせていただいたサイト一覧
- 投稿日:2019-11-12T12:45:42+09:00
PHPで週を扱う場合の注意点(ISO8601)
PHPに限った話ではないと思いますが、、、
他社実装のカレンダーにバグがあって、調査していて知ったので備忘録です。2018年12月最終週のカレンダーは以下の通り
日 月 火 水 木 金 土 30 31 1 2 3 4 5 これを踏まえ、2018年の12月30日(日)、12月31日(月)がそれぞれ年の何周目かを表示します。
まずは12月30日(日)から。echo (new \DateTime('2018-12-30 00:00:00'))->format('W');これは52が返ってきます。2018/12/30は年の52週目なんですね。
次に、2018/12/31について表示します。52を期待しましょう。echo (new \DateTime('2018-12-31 00:00:00'))->format('W');「01」
何ということでしょう。
2018/12/31は年の1週目ということです。これは、ISO8601に基づいているのでこのようになります。
同様に、2012/1/1は1周目ではなく52週目になります。プログラムで週を扱うときは注意が必要ですね。
【参照】
https://ja.wikipedia.org/wiki/ISO_8601#%E5%B9%B4%E3%81%A8%E9%80%B1%E3%81%A8%E6%9B%9C%E6%97%A5
- 投稿日:2019-11-12T11:54:30+09:00
Selenium php-webdriverでよく使うChromeオプション
php-webdriverを使ってChrome操作する時によく使うオプションメモ
selenium.php$options = new ChromeOptions(); //ヘッドレスモードを有効にする $options->addArguments( [ '--headless' ] ); //シークレットモードを有効にする $options->addArguments( [ '--incognito' ] ); //プロキシサーバーを設定する $proxy = "xxx.xxx.xxx.xxx:xxxx"; $options->addArguments( [ '--proxy-server='.$proxy] ); //ブラウザデータ保存先を任意指定する $tmp = "/tmp/chrome/"; $options->addArguments( [ 'user-data-dir='.$tmp]); //言語を指定する(日本語) $options->addArguments( [ '--lang=ja-JP' ] ); //UserAgentを指定する $UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36"; $options->addArguments( [ '--user-agent=' . $UserAgent ] ); //起動時の画面幅を指定する $width = 1280; $height = 1024; $options->addArguments( [ "window-size={$width},{$height}" ] ); $capabilities = DesiredCapabilities::chrome(); $capabilities->setCapability( ChromeOptions::CAPABILITY, $options ); try{ $driver = RemoteWebDriver::create( $this->selenium_server, $capabilities ); 〜〜以下略〜〜 //ヘッドレスモードでファイルをダウンロードする場合の問題を回避 $commands = parent::$commands; $commands['chromiumSendCommand'] = [ 'method' => 'POST', 'url' => '/session/:sessionId/chromium/send_command' ]; self::$commands = $commands; //DL場所を指定する $dl_path = "/tmp" $driver->execute('chromiumSendCommand', [ 'cmd' => 'Page.setDownloadBehavior', 'params' => [ 'behavior' => 'allow', 'downloadPath' => $dl_path ], ]);参考リンク
php-webdriver
- 投稿日:2019-11-12T11:41:06+09:00
クッキーとかセッションとか??
クッキーやセッションについて、本で読んでも「へぇ〜〜〜」みたいな感じで終わってしまうので手を動かして学んでみた。
そもそもクッキーとセッションとはなんなんだ?
クッキー
クライアント側にデータを保持する仕組みのこと。
セッション
サーバー側にデータを保持する仕組みのこと。
この保持したデータを取り出すにはsession_id
が必要。このsession_id
をcookieとしてクライアント側に保持する。
つまり、session_id
は鍵?でこれを使うことでサーバー側のデータにアクセスすることができる!なぜこうしてデータを保持しなければいけないかと言うと、例えば「ログイン状態を保持する」みたいな機能をつくりたいときに、HTTPにはデータを保持する仕組みがない。なので一時的/少量のデータをサーバーに持たせる必要がある。
PHPでわかるクッキーとセッション
サーバーにログインして
session_test.html
とかテキトーな名前のファイルを作成し、以下のページを見ながら
session状態を開始し、session_idを画面に表示する、セッション変数を決めて保存してみる
というのを一通りやってみた。
めちゃくちゃシンプルにサーバー側とクライアント側で保持する
部分がわかる!
ブラウザ側のURL窓の横からcookieを確認しておくとなお◎Railsでもやってみよう
coming soon...
- 投稿日:2019-11-12T09:48:12+09:00
【PHP】preg_replaceは参照記号も使える
軽いネタです。PHPには正規表現を用いた関数としてpreg_xxxxxというのがあります。代表的な関数としては
preg_match(パターンマッチング,対象文字列) //検索に用います preg_replace(パターンマッチング,置き換え文字,対象文字列) //置換に用いますなどがあります。では今回はこのpreg_replace関数についてです。そして、この関数は、エディタと同じように参照記号
$n
を使用することができます。実例
たとえば、住所録を整理していて、住所の表記揺れを修正したいとします。とりあえず、丁目は置いといて「xxのxx」や「xx番xx」を「xx-xx」に修正したい場合は置換記号を用いれば効果的に修正ができます。
PHP$ary_addr = [ "兵庫県尼崎市東七松町1丁目23−1", "兵庫県西宮市六湛寺町10の3", "兵庫県宝塚市東洋町1番1" ]; //ジェネレータの方が速いですが、一番記述としては無駄がないので… array_walk($ary_addr,function(&addr){ /*置換処理*/ });置換のおさらい
ここで置換のおさらいです。置換元のパターンマッチングは、置換させたくないパターンを
()
で囲みます。そして置換先のパターンに合わせ、参照記号$1,$2,$3,…
と設定していきます。たとえば、住所の「xxのxx」を「xx-xx」に置換したい場合はこのように記述します。
PHP$addr = preg_replace("/(\d+)の(\d+)/u",$1-$2,$addr);一方、置換箇所の設定に用いる
()
はグループ化にも用いる記号なので、置換させたい場所にも用いてしまう場合があります。たとえば、住所の「xxのxx」と「xx番地xx」を「xx-xx」に置換したい場合はこのように記述することになり、参照記号を、対応させるようにします(つまり、$2の部分が置換対象となる)。PHP$addr = preg_replace("/(\d+)(番地|の)(\d+)/u",$1-$3,$addr);これを踏まえて、実務的に搭載してみるとこのような記述になります。ここでは数字も半角に統一、そして「xx番」「xx番地」という表記も数値だけに修正しています。
PHP$ary_addr = [ "兵庫県尼崎市東七松町1丁目23−1", "兵庫県西宮市六湛寺町10の3", "兵庫県宝塚市東洋町1番1号" ]; //※ジェネレータの方が速いです array_walk($ary_addr,function(&addr){ //複数繰り返しに対応させる $addr = mb_convert_kana($addr,"n"); //数字を半角に while(preg_match("/\d+(番地?|の)\d+/u",$addr) != false){ $addr = preg_replace("/(\d+)(番地?|の)(\d+)/u","$1-$3",$addr); } $addr = preg_replace("/(\d+)(番地?|号)/u","$1",$addr); });これを出力すると以下のようになります。「丁目」も変更したい場合はそれもマッチングのグループ候補に入れるといいでしょう。
var_dump($ary_addr); /*以下のように置換されていきます 兵庫県尼崎市東七松町1丁目23−1", 兵庫県西宮市六湛寺町10-3", 兵庫県宝塚市東洋町1-1" */
- 投稿日:2019-11-12T09:48:12+09:00
【PHP】preg_replaceは参照記号も使える(正規表現での置換おさらい編)
初級者向けの軽いネタです。PHPには正規表現を用いた関数としてpreg_xxxxxというのがあります。代表的な関数としては
preg_match(パターン,対象文字列) //検索に用います preg_replace(パターン,置換文字,対象文字列) //置換に用いますなどがあります。では今回はこのpreg_replace関数についてです。そして、この関数は、エディタと同じように参照記号
$n
を使用することができます。なお、今回は正規表現の記法はある程度熟知しているものとして話を進めていきます。実例
たとえば、住所録を整理していて、住所の表記揺れを修正したいとします。とりあえず、丁目は置いといて「xxのxx」や「xx番xx」を「xx-xx」に修正したい場合は参照記号を用いれば効果的に修正ができます。
PHP$ary_addr = [ "兵庫県尼崎市東七松町1丁目23−1", "兵庫県西宮市六湛寺町10の3", "兵庫県宝塚市東洋町1番1" ];置換のおさらい
ここで置換のおさらいです。置換元のパターンは、置換させたくないパターンを
()
で囲みます。そして置換先の文字列に合わせ、グループ化記号を用いた個数だけ参照記号$1,$2,$3,…
と設定していきます。たとえば、住所の「xxのxx」を「xx-xx」に置換したい場合はこのように記述します。
PHP$addr = preg_replace("/(\d+)の(\d+)/u",$1-$2,$addr);一方、パターンマッチングに用いる
()
はパターンのグループ化にも用いる記号なので、置換させたい場所と区別できない場合があります。たとえば、住所の「xxのxx」と「xx番地xx」を「xx-xx」に置換したい場合は以下のように記述することになるので、その場合は参照記号を飛ばして対応させるようにします(つまり、$2の部分が置換対象となるので、参照記号を表記せずに、置換したい文字をそのまま記述する)。PHP$addr = preg_replace("/(\d+)(番地|の)(\d+)/u",$1-$3,$addr);実務的な使い方
これを踏まえて、実務的に搭載してみるとこのような記述になります。ここでは数字も半角に統一、そして「xx番」「xx番地」という表記も数値だけに修正しています。
PHP//市役所の住所録を格納 $ary_addr = [ "兵庫県尼崎市東七松町1丁目23−1", "兵庫県西宮市六湛寺町10の3", "兵庫県宝塚市東洋町1番1号" ]; //※配列の各値に対し、処理を行う。なお、ジェネレータの方が速いです array_walk($ary_addr,function(&addr){ //複数繰り返しに対応させる $addr = mb_convert_kana($addr,"n"); //数字を半角に //「xxのxx」などがなくなるように繰り返す while(preg_match("/\d+(番地?|の)\d+/u",$addr) != false){ $addr = preg_replace("/(\d+)(番地?|の)(\d+)/u","$1-$3",$addr); } $addr = preg_replace("/(\d+)(番地?|号)/u","$1",$addr); });これを出力すると以下のようになります。「丁目」も変更したい場合はそれもマッチングのグループ候補に入れるといいでしょう。
var_dump($ary_addr); /*以下のように置換されていきます 兵庫県尼崎市東七松町1丁目23−1", 兵庫県西宮市六湛寺町10-3", 兵庫県宝塚市東洋町1-1" */他言語での検証
正規表現で置換の働きを持つ関数ならば、概ね使えるようです(検証済)。ただし、色々と条件があるようなので、注意点をまとめておきました。
JavaScript
- jsの場合は置き換え文字列をダブルクォートで囲まないとエラーになる。
JavaScriptlet str = "11の3"; str = str.replace(/(\d+)(の|番地?)(\d+)/g,"$1-$3"); console.log(str); //11-3Perl
- 置換文字列を使用する場合はe修飾子を忘れないこと。
- パターンはダブルクォートで囲むとエラーになる。
- また、置換文字をダブルクォートで囲まないと計算式として処理される場合がある。
Perlmy $str = "11の3"; $str =~ s/(\d+)(の|番地?)(\d+)/"$1-$3"/e; print $str; #11-3Ruby
- パターンは囲っても囲まなくてもよい。
- ただし、置換文字はダブルクォートで囲まないとエラーが起きる場合がある。
Rubystr = "11の3" str.sub(/(\d+)(の|番地?)(\d+)/,"$1-$3") #gsubでもいいかも知れない print str #11-3Python
- パターンと置換文字をダブルクォートで囲まないとエラーになる。
Pythonstr = "11の3" str = str.replace("/(\d+)(の|番地?)(\d+)/","$1-$3") print(str) #11-3
- 投稿日:2019-11-12T09:25:26+09:00
Notice: Undefined indexエラー
気づけばプロ並みPHPを進める上で
$staff_code=$_POST['staffcode'])
の部分がなぜかNotice: Undefined indexエラーと表示されます。
綴りはあってるはずなのでググってみると
$_POSTなどで受け取る時によく発生するエラーらしい事が分かりました。Notice: Undefined indexエラーとは、未定義の配列の要素を使用した時に出るエラー。
$staff_code=(!empty($_POST['staffcode']))
↑に変更する事で解決しました。
参考URL:https://qiita.com/wakahara3/items/bb7c8d7a1673b161abe7
- 投稿日:2019-11-12T07:14:56+09:00
git cloneした時に発生したエラー
はじめに
【個人メモ】
自宅で作成・学習したアプリ環境を、会社でもデモ実施環境として使う為に、git cloneした時のお話。
難しいことはせず、ローカルサーバーで動かしたいだけだったのに、
php artisan serve
をして立ち上がらなかった。解決方法
composer install
をする。原因・理由
php artisan serve
をした際に読み込む、autoload.php
がなかったから。
その保存場所は、vendorフォルダ直下。
そのフォルダは、.gitignore
でgit管理外にしていたから、存在しなかったという理由。では、どうしてgit管理外にしていたか。
vendorフォルダとは何か?
composer による依存パッケージが入っている所。
どうして管理外にしていたか?
compose.json
やcomposer.lock
があれば落とす事が可能な為。ちなみに、
composer update
とcomposer install
の違いは下記の通り(で認識中)。composer install
composer.lock
に書かれているライブラリをインストールする。composer update
composer.json
をもとに最新版にアップデートする。
composer.lock
は存在していたので、composer install
を実行。おわりに
エンジニアになって1年、これがある程度さらっと頭に入るようになってきた。
それでもまだまだ知らない事が多い。
いまだに composer あたりは頭の中でふわっとしている。関連機会が生じた時に、深掘りしていければな〜と思う。
参照記事
- 投稿日:2019-11-12T03:03:34+09:00
PHPでページネーションを使って投稿一覧機能【初心者】
WEBページを作っていると記事を一覧表示させる事が多々あると思います。
その際に下の方までずーっとスクロールして見る長いページを作るのではなく、ページングして綺麗に見せようというのが今回の内容です。↑こういうやつです。
投稿の取得
投稿の取得
まずは投稿を取得していきます。
ここではテーブル名bbs_threadから投稿内容(thread)と投稿日(created)を取得しています。<?php // ドライバ呼び出しを使用して MySQL データベースに接続 $dsn = 'mysql:dbname=データベース名;charset=utf8mb4';//データベース名 $user = 'root';//設定していればそれを使う $password = 'root';//設定していればそれを使う try { $db = new PDO($dsn, $user, $password); } catch (PDOException $e) { echo "DB接続エラー: " . $e->getMessage() . "\n"; exit(); } $posts = $db->query('SELECT thread, created FROM bbs_thread ORDER BY created DESC'); function h($value){ return htmlspecialchars($value,ENT_QUOTES); }投稿された内容を表示するので、htmlspecialcharsもファンクション化しておきましょう。
次にHTMLを書いていきます。
foreachを使って取得した内容を表示します。<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> </head> <body> <?php foreach($posts as $post): ?> <h3><?php echo h($post['thread']); ?></h3> <p><?php echo h($post['created']); ?></p> <?php endforeach; ?> </body> </html>ページングするように変更
1ページあたり5件表示するように書き換えていきます。
//URLパラメータで指定された値を$pageに代入 $page = $_REQUEST['page']; //空だった場合は1とする if($page == ''){ $page = 1 ; } //最小のページ数を1とする $page = max($page,1); //最終ページの取得 //データ件数を取得してcntと名付ける $counts = $db->query('SELECT COUNT(*) AS cnt FROM bbs_thread'); $cnt = $counts->fetch(); //取得した件数を1ページで表示したい件数で割り、ceil関数で小数点以下を切り捨てる $maxPage = ceil($cnt['cnt']/5); //最大のページ数以上を表示させないようにする $page = min($page,$maxPage); $start = ($page -1)*5; //設定した値を使用するのでprepareを使用して値を取得 $posts = $db->prepare('SELECT thread, created FROM bbs_thread ORDER BY created DESC LIMIT ?, 5'); $posts->bindParam(1, $start, PDO::PARAM_INT); $posts->execute();HTMLではページが変わるように付け加えましょう。
<div> <ul class="pagination"> <li class="page-item"> <a class="page-link" href="test.php?page=<?php print($page - 1); ?>">«</a> </li> <?php for($i=1;$i<=$maxPage;$i++): ?> <?php if($_REQUEST['page'] == $i): ?> <li class="page-item active"> <a class="page-link" href="test.php?page=<?php echo $i ;?>"><?php echo $i ?></a> </li> <?php else : ?> <li class="page-item "> <a class="page-link" href="test.php?page=<?php echo $i ;?>"><?php echo $i ?></a> </li> <?php endif; ?> <?php endfor; ?> <li class="page-item"> <a class="page-link" href="test.php?page=<?php print($page + 1); ?>">»</a> </li> </ul> </div>最後にCSSで見た目を整えて、
.pagination{ list-style: none; display:flex; } .page-item{ margin:5px; }立派にページングできたのではないでしょうか。
最後に
コードに細かくコメントアウトしていたので説明を省略したところも多かったですが、ページネーションの方法を知らない人がこの投稿を読んで、出来るようになれたらいいなと思います。
読んでいただきありがとうございました。
- 投稿日:2019-11-12T01:18:56+09:00
ゼロから始める PHP + Laravel テスト覚書
Step 1 - 出力
まずはここから。
item.php<?php function getId() { return '4395bc36da6dca494ed7'; }
getId()
は、Qiita 当記事の ID を返す関数です。この関数が想定通りに動くかどうか、
echo getId();のようなコードを書いて確認していませんか?
$ php item.php 4395bc36da6dca494ed7この出力に期待していた値は "4395bc36da6dca494ed7" です。見比べて確認することはできますが、なぜ目視で「間違い探し」を始めてしまうのか。ここは、サイゼリヤではないのです。
Step 2 - 例外
目視ではなくコードで比較して、例外を投げてみます。
if (getId() !== '4395bc36da6dca494ed7') { throw new Exception(); }を実行すると、
$ php item.php
のように何も起こらず、正常な動作が確認できます。
一方、
getId()
の実装を誤り、- return '4395bc36da6dca494ed7'; + return '43956c36da6dca494ed7';の状態で実行すると、
$ php item.php Fatal error: Uncaught Exception in item.php:8のように異常な動作が確認できます。ただし、「例外」と「誤り」は異なる概念です。
Step 3 - 表明
PHP には assert() が用意されているため、比較と例外のコードを、
assert(getId() === '4395bc36da6dca494ed7');に置き換えて実行します。
$ php item.php
のように何も起こらず、正常な動作が確認できます。
getId()
の実装を誤った場合は、$ php item.php Warning: assert(): assert(getId() === '4395bc36da6dca494ed7') failed in item.php on line 7のように警告が確認できます。
Step 4 - 分割
ここまでのコードを「プロダクト」と「テスト」に分割して、下記の構成にします。
. ├── src │ └── Item.php └── tests └── ItemTest.phpsrc/Item.php<?php class Item { public function getId() { return '4395bc36da6dca494ed7'; } }tests/ItemTest.php<?php require_once __DIR__ . '/../src/Item.php'; $item = new Item(); assert($item->getId() === '4395bc36da6dca494ed7');テストコードを実行すると、
$ php tests/ItemTest.php
これまでと同じ動作が確認できます。分割したことで、コードの責務が明確になりました。
Step 5 - 仕様
getId()
の仕様を考えて、
- 文字列を返す
- 文字列は 16 進数表記
- 文字列の長さは 20 文字
を想定したテストを書いてみます。
tests/ItemTest.php<?php require_once __DIR__ . '/../src/Item.php'; $item = new Item(); assert(is_string($item->getId())); assert(ctype_xdigit($item->getId())); assert(mb_strlen($item->getId()) == 20);
getId()
は "4395bc36da6dca494ed7" を返すので、$ php tests/ItemTest.php
問題なくテストは通ります。テストコードはバグを見つけるだけでなく、仕様を示す役割も担います。
Step 6 - PHPUnit
PHPUnit は、テスト用のフレームワークです。
$ composer require --dev phpunit/phpunitでインストールします。
テストコードを PHPUnit を使って書き直すと、下記のようになります。
tests/ItemTest.php<?php require_once __DIR__ . '/../src/Item.php'; use PHPUnit\Framework\TestCase; class ItemTest extends TestCase { public function setUp(): void { $this->item = new Item(); } public function testGetId_string() { $this->assertIsString($this->item->getId()); } public function testGetId_hexadecimal() { $this->assertStringMatchesFormat('%x', $this->item->getId()); } public function testGetId_length20() { $this->assertEquals(20, mb_strlen($this->item->getId())); } }PHPUnit を実行すると、
$ ./vendor/bin/phpunit tests PHPUnit 8.4.2 by Sebastian Bergmann and contributors. ... 3 / 3 (100%) Time: 149 ms, Memory: 4.00 MB OK (3 tests, 3 assertions)のようにテスト結果が確認できます。
test*
の関数がテスト項目で、setUp()
は各テストの前に実行されます。比較には PHPUnit のアサーションを使用します。1. Assertions — PHPUnit latest Manual
Step 7 - Mockery
Mockery は、モック用のフレームワークです。
$ composer require --dev mockery/mockeryでインストールします。
ここでは Item クラスに、
public $user = null; public function __get($name) { return $name === 'author' ? $this->user->getName() : null; }というコードを追加して、ユーザオブジェクトとの連携を考えてみます。
「author プロパティは "Name" を返す」というテストコードは、
public function testGetAuthor() { $this->assertEquals('Name', $this->item->author); }のようになりますが、
$user
が null のためテストできません。このテストコードに Mockery を使うと、
public function testGetAuthor() { $mock = Mockery::mock('User'); $mock->shouldReceive('getName')->andReturn('Name')->once(); $this->item->user = $mock; $this->assertEquals('Name', $this->item->author); } public function tearDown(): void { Mockery::close(); }のように書いてテストができます。
Step 8 - Faker
Faker は、ダミーデータを生成するライブラリです。
$ composer require --dev fzaninotto/fakerでインストールします。
Faker でユーザ名を置き換えると、
public function testGetAuthor() { $faker = Faker\Factory::create(); $name = $faker->name; $mock = Mockery::mock('User'); $mock->shouldReceive('getName')->andReturn($name)->once(); $this->item->user = $mock; $this->assertEquals($name, $this->item->author); }のように書いて、ランダムなユーザ名でテストができます。
日本の個人情報を生成したければ、
$faker = Faker\Factory::create('ja_JP'); $faker->name; // 名前 $faker->address; // 住所 $faker->phoneNumber; // 電話番号 $faker->email; // メールアドレスのように書けます。
Step 9 - phpunit-watcher
phpunit-watcher は、コード変更時に PHPUnit を実行するツールです。
$ composer require --dev spatie/phpunit-watcherでインストールします。
$ ./vendor/bin/phpunit-watcher watch tests
で監視を始めると、コードを変更するたびに PHPUnit の実行結果を確認できます。
Step 10 - php-code-coverage
sebastianbergmann/php-code-coverage
php-code-coverage は、カバレッジを計測するライブラリです。
phpdbg を使うと、
$ phpdbg -qrr ./vendor/bin/phpunit tests --whitelist src --coverage-text Code Coverage Report: 2019-11-10 23:59:59 Summary: Classes: 100.00% (1/1) Methods: 100.00% (2/2) Lines: 100.00% (2/2) Item Methods: 100.00% ( 2/ 2) Lines: 100.00% ( 2/ 2)のようにカバレッジを確認できます。
HTML 形式で確認したければ、
$ phpdbg -qrr ./vendor/bin/phpunit tests --whitelist src --coverage-html coverageで coverage ディレクトリに出力できます。
Step 11 - php-webdriver
php-webdriver は、Selenium を操作するライブラリです。
$ composer require --dev facebook/webdriverでインストールします。
Selenium は、selenium/standalone-chrome の Docker イメージを使えば、
$ docker run -p 4444:4444 selenium/standalone-chromeで準備ができます。
ここでは Qiita のタイトルを確認するテストを書いてみます。
tests/BrowserTest.php<?php use PHPUnit\Framework\TestCase; use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\Remote\DesiredCapabilities; class BrowserTest extends TestCase { public function testQiita() { $driver = RemoteWebDriver::create('http://localhost:4444/wd/hub', DesiredCapabilities::chrome()); $driver->get('https://qiita.com/'); $this->assertEquals('Qiita', $driver->getTitle()); $driver->quit(); } }$ ./vendor/bin/phpunit tests/BrowserTest.php
でブラウザ経由のテストができます。
Step 12 - Laravel
ここからは、Laravel アプリケーションのテストを取り扱います。
$ composer create-project --prefer-dist laravel/laravel exampleで作成したプロジェクトには、tests ディレクトリが含まれており、
- tests/Unit/ExampleTest.php
- tests/Feature/ExampleTest.php
がサンプルのテストコードです。
PHPUnit を使っているため、
$ ./vendor/bin/phpunit
でテストができます。設定は
phpunit.xml
を参照してください。Laravel x PHPUnit
サンプルのテストコードは、
tests/TestCase.php
を継承しています。tests/TestCase.php<?php namespace Tests; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; abstract class TestCase extends BaseTestCase { use CreatesApplication; }
use CreatesApplication
はtests/CreatesApplication.php
を利用して、テスト対象の Laravel アプリケーションを立ち上げます。Illuminate\Foundation\Testing\TestCase を覗くと、
PHPUnit\Framework\TestCase
のサブクラスであることが確認できます。Step 13 - Laravel (HTTP)
Laravel は、HTTP テストをサポートします。
tests/Feature/ExampleTest.php
では、public function testBasicTest() { $response = $this->get('/'); $response->assertStatus(200); }と書かれており、
/
のレスポンスを確認しています。HTTP リクエスト
$response = $this->get('/');HTTP リクエストは、Illuminate\Foundation\Testing\Concerns\MakesHttpRequests で実装されており、基本的な HTTP メソッドが揃っています。
また、Illuminate\Foundation\Testing\Concerns\InteractsWithSession でサポートされているため、
$response = $this->withSession(['key' => 'value'])->get('/');のようにセッションを設定することもできます。
HTTP レスポンス アサーション
$response->assertStatus(200);HTTP リクエストは、Illuminate\Foundation\Testing\TestResponse を返します。このオブジェクトには HTTP レスポンス用のアサーションが実装されているため、
$response
から各アサーションを利用します。
assertStatus()
の実装は、public function assertStatus($status) { $actual = $this->getStatusCode(); PHPUnit::assertTrue( $actual === $status, "Expected status code {$status} but received {$actual}." ); return $this; }となっており、各アサーションは PHPUnit のアサーションのラッパーです。
Step 14 - Laravel (Database)
Laravel は、データベーステストをサポートします。
phpunit.xml
に、<server name="APP_ENV" value="testing"/>が設定されているため、テスト時のデータベースは
.env.testing
で指定します。ここでは、
$ cp .env .env.testingで
.env.testing
を用意して、DB_CONNECTION=sqlite
に書き換えて、$ touch database/database.sqlite $ php artisan migrate --env=testingでテスト用のデータベースを準備します。
準備が整えば、
public function testBasicTest() { $user = factory(\App\User::class)->create(); $this->assertDatabaseHas('users', ['email' => $user['email']]); }のように書いて、データベースへの保存をテストできます。
Eloquent ファクトリ
$user = factory(\App\User::class)->create();Laravel には、テストデータを生成する
factory()
が用意されており、ここではdatabase/factories/UserFactory.php
を基にしています。ファイルを覗くと、Faker を使っていることが確認できます。ファクトリの雛型は、
$ php artisan make:factory ExampleFactory
で作成できます。
データベース アサーション
$this->assertDatabaseHas('users', ['email' => $user['email']]);
Tests\TestCase
には、Illuminate\Foundation\Testing\Concerns\InteractsWithDatabase でデータベース用のアサーションが追加されているため、$this
から各アサーションを利用します。テスティング トレイト
テストを繰り返すとデータが蓄積してしまいますが、サンプルのテストコードで定義されている、
use Illuminate\Foundation\Testing\RefreshDatabase;を
class ExampleTest extends TestCase { use RefreshDatabase;のように適用すれば、PHPUnit の
setUp()
時にデータベースを初期化できます。Illuminate\Foundation\Testing\RefreshDatabase を覗くと、トレイトで実装されていることが確認できます。その他、
- Illuminate\Foundation\Testing\DatabaseMigrations
- Illuminate\Foundation\Testing\DatabaseTransactions
- Illuminate\Foundation\Testing\WithoutMiddleware
- Illuminate\Foundation\Testing\WithoutEvents
- Illuminate\Foundation\Testing\WithFaker
のトレイトが
Tests\TestCase
に適用できます。Step 15 - Laravel (Authentication)
Laravel は、認証テストをサポートします。
Laravel 6 から
artisan make:auth
が無くなったため、$ composer require --dev laravel/ui $ php artisan ui vue --authで認証機能を準備します。
準備が整うと
/home
に認証が求められるため、public function testBasicTest() { $response = $this->get('/home'); $response->assertStatus(200); }のようなテストは失敗します。
認証済みの状態でテストしたければ、
public function testBasicTest() { $user = factory(\App\User::class)->make(); $response = $this->actingAs($user)->get('/home'); $response->assertStatus(200); }のように書きます。
認証 アサーション
$this->assertAuthenticated();
Tests\TestCase
には、Illuminate\Foundation\Testing\Concerns\InteractsWithAuthentication で認証用のアサーションが追加されているため、$this
から各アサーションを利用します。Step 16 - Laravel (Mock)
Laravel は、モックをサポートします。
サービス
app/Services/UserServices.php<?php namespace App\Services; use App\User; class UserService { public function getUsers() { return User::all(); } }というサービスの
getUsers()
は、public function testBasicTest() { $this->mock(\App\Services\UserService::class, function($mock) { $user = factory(\App\User::class)->make(); $mock->shouldReceive('getUsers')->andReturn([$user])->once(); }); $userService = app(\App\Services\UserService::class); $this->assertCount(1, $userService->getUsers()); }のように、
mock()
で置き換えられます。Illuminate\Foundation\Testing\Concerns\InteractsWithContainer を覗くと、Mockery を使っていることが確認できます。
ファサード
ファサードのモック機能を使うと、
public function testBasicTest() { Cache::shouldReceive('get')->with('key')->andReturn('value'); $this->assertEquals('value', Cache::get('key')); }のように書けます。
Illuminate\Support\Facades\Facade は、
::shouldReceive()
で Mockery オブジェクトを返します。フェイク
特定のファサードには
fake()
が用意されているため、public function testBasicTest() { Storage::fake('s3'); $file = UploadedFile::fake()->image('image.png'); $file->storeAs('images', $file->name, 's3'); Storage::disk('s3')->assertExists('images/image.png'); }のように書けます。
Step 17 - Laravel Dusk
Laravel Dusk は、E2E テスト用のフレームワークです。
$ composer require --dev laravel/dusk $ php artisan dusk:installでインストールします。
- tests/DuskTestCase.php
- tests/Browser
が追加され、
tests/Browser/ExampleTest.php
がサンプルのテストコードです。public function testBasicExample() { $this->browse(function (Browser $browser) { $browser->visit('/') ->assertSee('Laravel'); }); }
Laravel\Dusk\Browser
に Laravel\Dusk\Concerns\MakesAssertions でブラウザ用のアサーションが追加されているため、$browser
から各アサーションを利用します。$ php artisan dusk
で ChromeDriver 経由のテストができます。
Selenium
tests/DuskTestCase.php
は、php-webdriver を使っているため、public static function prepare() { //static::startChromeDriver(); } protected function driver() { return RemoteWebDriver::create('http://localhost:4444/wd/hub', DesiredCapabilities::chrome()); }と書けば、Selenium に置き換えることができます。
Step Ex - Kahlan
Kahlan は、BDD テスト用のフレームワークです。
$ composer require --dev kahlan/kahlanでインストールします。
Kahlan を使うと、テストを describe-it 構文で書けます。
describe('Example', function() { it('passes if true === true', function() { expect(true)->toBe(true); }); });Laravel への導入方法は、「Laravel のテストを describe-it でいい感じに書く」を参考にしてください。