- 投稿日:2019-03-05T22:58:47+09:00
独自オプションを作成してShellを実装してみた
環境
- PHP 7.2.6
- CakePHP 3.6.13
やりたい事
- DBの値更新
- オプション無しの場合
- 対象カラムの値全更新
- オプション有りの場合
- 対象カラムの指定箇所のみ更新
Shellクラス作成
- CakePHPのbakeコマンドでShellクラス作成
$ bin/cake bake shell sample Creating file /vagrant/yourmystar.jp/src/Shell/SampleShell.php Wrote `/vagrant/yourmystar.jp/src/Shell/SampleShell.php` Baking test case for App\Shell\SampleShell ... Creating file /vagrant/yourmystar.jp/tests/TestCase/Shell/SampleShellTest.php Wrote `/vagrant/yourmystar.jp/tests/TestCase/Shell/SampleShellTest.php`
- 下記のようなShellが作成される
getOptionParser
- オプションを定義
- 参考:CakePHP 3.7 Red Velvet Cookbookオプションパーサー
main
- 実行処理を定義
実装
<?php namespace App\Shell; use Cake\Console\Shell; /** * Sample shell command. */ class SampleShell extends Shell { /** * Manage the available sub-commands along with their arguments and help * * @see http://book.cakephp.org/3.0/en/console-and-shells.html#configuring-options-and-generating-help * * @return \Cake\Console\ConsoleOptionParser */ public function getOptionParser() { $parser = parent::getOptionParser(); $parser->addOption('target_id', [ 'help' => '更新対象のIDを指定する' ]); return $parser; } public function initialize() { parent::initialize(); $this->loadModel('Hoge'); } /** * main() method. * * @return bool|int|null Success or error code. */ public function main() { $offset = 0; $executionDate = date('Y-m-d H:i:s'); $this->out($executionDate . " [Start]"); while (true) { $samples = $this->Hoge ->find() ->offset($offset) ->limit(100); if (isset($this->params['target_id'])) { $samples->where(['id IN' => explode(',', $this->params['target_id'])]); } $samples = $samples->toArray(); if (empty($samples)) { break; } foreach ($samples as $sample) { $sample->flg = 1; $this->Hoge->save($sample); $this->out('Update Id:' . $sample->id); } $offset += 100; } $this->out($executionDate . " [End]"); } }解説
- getOptionParser
- addOptionメソッド
- オプションを定義する
- 参考:オプションの利用
- DBからデータ取得
- HogeTableからデータを100件ずつ取得
- オプションが指定された場合、where句に指定される
- explodeでIDの複数指定を可能にしている
- カラムの値更新
- flgカラムの値を1に更新
- offsetの値追加
- 100件ずつ取得するので、次の100を取得するために
$offset += 100;
をする※変数名は基本「仮」なのでご了承下さい。
実行結果
- 通常実行
$ bin/cake sample 2019-03-05 20:37:52 [Start] Update Id:1 Update Id:2 ・ ・ ・ 2019-03-05 20:37:52 [End]
- オプション指定実行
$ bin/cake sample --target_id=1 2019-03-05 20:39:51 [Start] Update Id:1 2019-03-05 20:39:51 [End]
- オプション複数指定実行
$ bin/cake sample --target_id=1,2,3 2019-03-05 20:39:51 [Start] Update Id:1 Update Id:2 Update Id:3 2019-03-05 20:39:51 [End]
- help表示
$ bin/cake sample --help Usage: cake sample [-h] [-q] [--target_id] [-v] Options: --help, -h Display this help. --quiet, -q Enable quiet output. --target_id 更新対象のIDを指定する // addOptionメソッドで追加したオプション --verbose, -v Enable verbose output.簡単!!!
おまけ
Twitterやってます!外部のエンジニアの方ともどんどん繋がりたいと考えていますので、是非フォローして頂ければと思います!@Tatsuo96
ブログ始めました!
https://note.mu/tatsuo_iriyama
- 投稿日:2019-03-05T22:32:47+09:00
PHPではネストした三項演算子は左から評価される
PHPの三項演算子はネスト、つまり三項演算子の項に三項演算子を使った式を使うことができるが、PHPの三項演算子は右から順に評価されるので注意が必要だ。
いろんな言語で次のようにネストした三項演算式が書ける。
true ? 1 : true ? 2 : true ? 3 : 4多くの言語1は、次のように右から評価していく。
true ? 1 : (true ? 2 : (true ? 3 : 4)) true ? 1 : (true ? 2 : 3 )) true ? 1 : 2 1しかしPHPは、左から評価していく。
((true ? 1 : true) ? 2 : true) ? 3 : 4 ( 1 ? 2 : true) ? 3 : 4 2 ? 3 : 4 3したがって、例えば、1なら「one」、2なら「two」、3なら「three」、それ以外なら「more」をマッピングしたい気持ちで次のような三項演算子の式を書くと思いがけない罠にはまる。
$value = 2; var_dump( $value === 1 ? 'one' : $value === 2 ? 'two' : $value === 3 ? 'three' : 'more' ); //=> string(5) "three"上のコードは次のように計算された結果、"three"が答えになる。
(($v === 1 ? 'one' : $v === 2) ? 'two' : $v === 3) ? 'three' : 'more' ^^ ((2 === 1 ? 'one' : $v === 2) ? 'two' : $v === 3) ? 'three' : 'more' ^------^ ((false ? 'one' : $v === 2) ? 'two' : $v === 3) ? 'three' : 'more' ^^ ((false ? 'one' : 2 === 2) ? 'two' : $v === 3) ? 'three' : 'more' ^------^ ((false ? 'one' : true ) ? 'two' : $v === 3) ? 'three' : 'more' ^---------------------------^ (true ? 'two' : $v === 3) ? 'three' : 'more' ^------------------------------------------------^ 'two' ? 'three' : 'more' ^-------------------------------------------------------------------^ 'three'上記のようなマッピングをしたい場合は、ifやswitch、連想配列など他の手立てを使ったほうがいい。
$value = 2; var_dump( (function (int $value): string { if ($value === 1) return 'one'; if ($value === 2) return 'two'; if ($value === 3) return 'three'; return 'more'; })($value) ); //=> string(3) "two" var_dump( (function (int $value): string { switch ($value) { case 1: return 'one'; case 2: return 'two'; case 3: return 'three'; default: return 'more'; } })($value) ); //=> string(3) "two" var_dump( (function (int $value): string { return [ 1 => 'one', 2 => 'two', 3 => 'three' ][$value] ?? 'more'; })($value) ); //=> string(3) "two"
Java, Swift, Ruby, JavaScriptなど ↩
- 投稿日:2019-03-05T22:09:16+09:00
python, ruby, php, node のループの速さ
今日も楽しいマイクロベンチマーク。
某所で python の for ループが遅いという話を聞いたので、そうなの? と思って他の言語と比べてみた。
ソース
python3
python3import sys r=0 for i in range(int(sys.argv[1])): r+=1 print(r)PHP
<?php $len = (int)$argv[1]; $r=0; for( $i=0 ; $i<$len ; ++$i ){ ++$r; } echo($r);node.js
node.jsconst len = process.argv[2]|0; let r=0; for( let i=0 ; i<len ; ++i ){ ++r; } console.log(r);ruby
ruby2.6n=ARGV[0].to_i r=0 n.times do r+=1 end p r測る人
測る人はわりとやる気ない感じで、
Benchmark.realtime
を使っている。bench.rbrequire "benchmark" require "pp" COMMANDS = [ [ "php for.php", "php" ], [ "node for_pp.js", "node" ], [ "python3 for_range.py", "python3" ], [ "ruby times.rb", "ruby" ], ] COUNTS = (10..27).map{ |e| 2**e } File.open( "result.csv", "w" ) do |f| f.puts( (["tick"]+COMMANDS.map{ |cmd| cmd[1] }).join(",") ) COUNTS.each do |count| s=([count]+COMMANDS.map{ |cmd| Benchmark.realtime{ %x(#{cmd[0]} #{count}) } }).join(",") f.puts(s) puts(s) end end各言語のファイル名がいい加減なのがバレるね。
結果
結果は下記グラフの通り。
両対数グラフ注意。測る人のソースコードを見ると分かる通り、プログラムの起動時間を含んでいる。
10万回ぐらい回しても、起動時間の影に隠れて殆ど見えないということがわかる。1.34億回回すのに要する時間を、node.js を 1.00 として表にすると:
php node python3 ruby Benchmark.realtime そのまま 6.13 1.00 61.72 28.83 起動時間らしきものを減算 10.56 1.00 108.11 50.13 「起動時間らしきものを減算」は、1.34億回の結果から 1024回の結果を減じたもの。
こんな感じ。node 速いね。
そして噂のとおり、python3 は遅いのであった。あと。PHP だけ起動が速いらしい。そういうものか。
- 投稿日:2019-03-05T21:14:44+09:00
SynologyのNASでrep2のImageCache2を動作させる際のTips
SynologyのNASでImageCache2(ic2)を動作させる
SynologyのNASは非常に高機能で、HTTPサーバーとして動作させることができるのでrep2を動かそうとした際に、ic2の設定でハマったのでその部分だけ書きます。
- Web Station
- Apache2.2
- PHP5.6
- phpMyAdmin
- MariaDB10
をインストールし、phpMyAdminでMariaDB10データベース上にユーザーとデータベースを作成し終わったところから始めます。時間があるときに上記の手順についても書き加えるかもしれません。
php extensionをオンにする
Web StationのPHP設定から、拡張gdとmysqliにチェックを入れて保存し、全般設定のPHP項目でこのプロファイルを選択しておきます。
rep2のセットアップ
ここではパーソナルウェブサイトをオンにして、ユーザーホームフォルダ下のwwwフォルダにセットアップする方法で説明します。ルートフォルダに作成する場合は、作業フォルダ/volume1/homes/[user]/wwwを/volume1/webに読み替えてください。パーソナルウェブサイトをオンにするためにはApache2.2とPHP5.6の組み合わせが必要となるので、そのバージョンでのセットアップ方法になります。
/volume1/homes/[user]/www$ git clone git://github.com/open774/p2-php.git $ cd p2-php/volume1/homes/[user]/www/p2-php$ curl -O http://getcomposer.org/composer.pharここまではreadmeに書かれている通りで問題ありませんが、ここから注意が必要です。
この段階ではまだ問題にはなりませんが、今後全てのCLIでのphpコマンドはphp 56(PHP7.0を使う場合はphp 70、PHP7.2ならばphp 72)としてください。
実はSynologyのNASをブラウザから操作するためのアプリケーションDSMは、内部にHTTPサーバーの他、PHPも走っています。そして、コマンドでphpとのみ記載すると、パッケージセンターからインストールしたものではなく、デフォルトのPHPが作動してしまいます。これがic2のセットアップ時に問題となります。
/volume1/homes/[user]/www/p2-php$ php56 -d detect_unicode=0 composer.phar install $ chmod 0777 data/* rep2/ic2エラーがないことを確認します
/volume1/homes/[user]/www/p2-php$ php56 scripts/p2cmd.php check PHP Version: 5.6.39: OK PHP Extensions: curl: Not loaded dom: OK json: OK libxml: OK mbstring: OK pcre: OK pdo: OK pdo_sqlite: Not loaded phar: Not loaded session: OK spl: OK zlib: OK php.ini directives: safe_mode = : OK register_globals = : OK magic_quotes_gpc = : OK mbstring.encoding_translation = 0: OK session.auto_start = 0: OKrep2のセットアップは終わりました。これでrep2を使うだけなら問題なく使えるでしょう。
ImageCache2(ic2)のセットアップ
いよいよic2のセットアップです。readmeを参考にして、自分が作成したmysqlのデータベース、ユーザー、パスワードに基づいてconf/conf_admin_ex.inc.phpとconf/conf_ic2.inc.phpを編集します。ここでは、画像処理エンジンを先ほどチェックした拡張機能'gd'でセットアップします。
/vollume1/homes/[user]/www/p2-php$ php scripts/ic2.php setup enabled=3 dsn='mysqli://[user]:[password]@localhost:3307/imagecache' driver='gd' [Exception] Extension gd is not loaded setup [--check-only] [--pg-trgm="..."]あれ?さっきgdにチェック入れたのにgdがロードされません。
ここでまず1回躓きます。先ほど書いた通り、CLIでphpとだけ記載するとDSMのデフォルトのPHPが動作し、このPHPにはgd.soが読み込まれないようになっている(gd.so自体は実装されているがphp.iniに記載されていない)ためです。
ならば、と。
/vollume1/homes/[user]/www/p2-php$ php56 scripts/ic2.php setup enabled=3 dsn='mysqli://[user]:[password]@localhost:3307/imagecache' driver='gd' [Exception] Extension gd is not loaded setup [--check-only] [--pg-trgm="..."]えええぇぇぇ…PHP5.6のプロファイルでgdにチェックを入れたのになんで読み込まれないの?
本当にgdは読み込まれていないのか?
$ php56 -r 'phpinfo();' | grep gd $読み込まれてないし!
なんでだ?$ cd /volume1/homes/[user]/www/ $ vi phpinfo.phpphpinfo.php<?php phpinfo(); ?>結論
/volume1/homes/[user]/web/p2-php$ php56 -dextension=gd.so scripts/ic2.php setup enabled=3 dsn='mysqli://[user]:[password]@localhost:3307/imagecache' driver='gd' Image Driver: OK Database: OK Table 'imgcache' created Table 'ic2_errors' created Table 'ic2_blacklist' created Index 'idx_imgcache_uri' created Index 'idx_imgcache_time' created Index 'idx_imgcache_unique' created Index 'idx_ic2_errors_uri' created Index 'idx_ic2_blacklist_uri' created Index 'idx_ic2_blacklist_unique' createdパッケージセンターからインストールしたPHPは、CLIから使用する場合、カスタマイズされたphp.ini(user.ini)を読み込まないということでした。なので、gd.soを動作させるためにはコマンドで明示的に打ち込む必要があります。このセットアップを終えてしまえば、あとはHTTPサーバーからのアクセスのみとなるので、特別な作業は必要ありません。
ここにたどり着くのに非常に時間がかかりました。SynologyのNASでgdを含めた拡張を読み込ませるために、modulesフォルダにあるgd.soをコピーしたり、php.iniをコピー&編集したりという方法で解決している場合もありましたが、この件に関しては解決することができませんでした。
一度コマンドで読み込むだけのことなので、この方法が一番良いと思います。お疲れ様でした。
※ちなみにImageMagickを使用する場合は拡張機能を読み込む必要がないので、こんな作業をすることなく使用することができたりします。あくまでgdやimagickを使いたい人向けです。
- 投稿日:2019-03-05T20:11:10+09:00
SwiftからPHPへファイルをPOSTする
iOS側のコード
//アップロードしたいファイルのパスを取得 let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("test").appendingPathExtension("csv") var req = URLRequest(url: URL(string: "http://{ホストのアドレス}/upload.php")!) req.httpMethod = "POST" URLSession.shared.uploadTask(with: req, fromFile: path) { (data, res, err) in print(data, res, err) }.resume()PHP側のコード
<?php date_default_timezone_set('Asia/Tokyo'); //タイムゾーンを設定する $date = date("YmdHis"); //時刻の取得 $file = file_get_contents("php://input"); //RequestのBodyを取得 $fileName = "./${date}.csv"; //保存する名前を取得 file_put_contents($fileName,$file); //保存 ?>Swift、PHPともにエラーハンドリングなどは全て省略されていますのでプロダクションとして使う場合はもう少し手を加えてあげる必要がありますのでご注意を!!
- 投稿日:2019-03-05T20:10:07+09:00
初めて使ったSQLiteで躓いたこと
ことの始まり
今現在自身が公開していたサービスの再開発をしており、Laravel+Vue+Vuerouterでやっているのですが、開発の途中でテストを行ったところ躓きました。
躓いた箇所
APIの作成をして
artisan
でテストをしたところ以下のようなエラーが出ましたconsole$ ./vendor/bin/phpunit --testdox PHPUnit 7.5.6 by Sebastian Bergmann and contributors. Unit\Example ✔ Basic test Feature\Example ✔ Basic test Feature\RegisterApi ✘ Should 新しいユーザーを作成して返却する /*中略*/ Caused by │ PDOException: could not find driver /*中略*/ ERRORS! Tests: 3, Assertions: 2, Errors: 1.ん?
ドライバが入ってない・・・
そりゃ初めてSQLiteを使ったんで入ってないですよね(^_^;)
ずっとMySQLを使っていて久しぶりに新しいDBMSを入れたので失念していました。解決法
ドライバを入れましょう!
console$ sudo apt install php-pdo-sqlite // SQLiteのPDOドライバをインストール
- 投稿日:2019-03-05T18:57:45+09:00
PHPのコード解析「Psalm」の使い方
本稿ではPHPのコード解析ツールPsalm(サーム /sάːm/)について紹介する。
本稿の内容
本稿では次の内容を説明する。
- Psalmがしてくれること
- Psalmの導入手順
- Psalmの設定
- Psalmの実行方法
Psalmがしてくれること
PsalmはPHPの静的コード解析(static code analysis)ツールで、PHPコードを解析して問題点をあぶり出してくれる。Psalmで発見できる問題は主に次のものがある:
- 廃止予定(deprecated)の関数、メソッド、クラスの利用。
- 配列キーの重複。
- 型の不整合。戻り値の型宣言と異なる型の
return
など。- アクセスできないメンバ変数やメソッドへのアクセス。
private
や@internal
など。コンパイル型言語であれば、こうした解析はコンパイラの仕事だったりするが、PHPはスクリプト言語なのでコンパイラが存在しない。こうした開発ツールの欠如を補うのがPsalmの役割と考えられる。
Psalmで見つけた問題点はPsalterというツールで自動修正できるものもあるが、Psalterの説明は本稿では取り上げない。
なお、このツールはvimeoが作っている。
Psalmの導入手順
PsalmはComposerでインストールする:
composer require --dev vimeo/psalmインストールできたら設定ファイルpsalm.xmlを作るために初期化コマンドを実行する:
./vendor/bin/psalm --init src 3srcは解析対象のコードがあるディレクトリ、3は設定レベル。1〜8の値を渡す。1は最も厳密な解析を行う。8は最もゆるい。
解析を実行する:
./vendor/bin/psalmPsalmの設定
Psalmはデフォルトの設定でもさまざまな問題を検出してくれるが、設定ファイル
psalm.xml
に設定を加えることで検査の厳密さを上げたり、振る舞いを変えたりすることができる。
<psalm />
の設定totallyTyped
totallyTyped
にtrue
をセットすると、Psalmが型を推測できないコードをエラーとして扱うようになる。psalm.xml<psalm totallyTyped="true" />strictBinaryOperands
文字列結合と数値の足し算を厳密にチェックする。つまり、文字列に対する
+
と、数値に対する.
の使用をエラーとして扱うようになる。psalm.xml<psalm strictBinaryOperands="true" >allowPhpStormGenerics
PhpStormのために配列の型の情報をdocblockに
ArrayIterator|string[]
などと書くことがあるが、allowPhpStormGenerics
をtrue
にするとその情報を尊重するようになる。psalm.xml<psalm allowPhpStormGenerics="true" >まとめ
本稿では、PHPのコード解析ツールについて紹介した。
- PsalmはPHPコードを解析して問題点をあぶり出す。
- 設定することでより厳格にチェックすることができる。
質問があればコメントください。
- 投稿日:2019-03-05T18:07:51+09:00
[初心者]Laravelのインストール
# composerのインストール satounoMacBook-Pro:~ satou$ brew install homebrew/core/composer ==> Downloading https://getcomposer.org/download/1.8.4/composer.phar ######################################################################## 100.0% ?/usr/local/Cellar/composer/1.8.4: 3 files, 1.8MB, built in 2 minutes 36 seconds# laravelのインストール satou$ composer global require "laravel/installer" Changed current directory to /Users/satou/.composer Using version ^2.0 for laravel/installer ./composer.json has been created Loading composer repositories with package information Updating dependencies (including require-dev) Package operations: 12 installs, 0 updates, 0 removals - Installing symfony/process (v4.2.4): Downloading (100%) - Installing symfony/polyfill-ctype (v1.10.0): Downloading (100%) - Installing symfony/filesystem (v4.2.4): Downloading (100%) - Installing symfony/polyfill-mbstring (v1.10.0): Downloading (100%) - Installing symfony/contracts (v1.0.2): Downloading (100%) - Installing symfony/console (v4.2.4): Downloading (100%) - Installing guzzlehttp/promises (v1.3.1): Downloading (100%) - Installing ralouphie/getallheaders (2.0.5): Downloading (100%) - Installing psr/http-message (1.0.1): Downloading (100%) - Installing guzzlehttp/psr7 (1.5.2): Downloading (100%) - Installing guzzlehttp/guzzle (6.3.3): Downloading (100%) - Installing laravel/installer (v2.0.1): Downloading (100%) symfony/contracts suggests installing psr/cache (When using the Cache contracts) symfony/contracts suggests installing psr/container (When using the Service contracts) symfony/contracts suggests installing symfony/cache-contracts-implementation symfony/contracts suggests installing symfony/service-contracts-implementation symfony/contracts suggests installing symfony/translation-contracts-implementation symfony/console suggests installing psr/log (For using the console logger) symfony/console suggests installing symfony/event-dispatcher symfony/console suggests installing symfony/lock guzzlehttp/guzzle suggests installing psr/log (Required for using the Log middleware) Writing lock file Generating autoload files# パスを通す?いまいち理解してない vi ~/.bashrc satounoMacBook-Pro:~ satou$ source ~/.bashrc# Laravelのバージョン確認 satounoMacBook-Pro:helloapp satou$ php artisan --version =>Laravel Framework 5.8.2# 新規アプリケーションの作成 taskappはアプリケーション名 satounoMacBook-Pro:projects satou$ laravel new taskapp Crafting application... Loading composer repositories with package information Installing dependencies (including require-dev) from lock file Package operations: 74 installs, 0 updates, 0 removals - Installing doctrine/inflector (v1.3.0): Loading from cache - Installing doctrine/lexer (v1.0.1): Loading from cache - Installing dragonmantank/cron-expression (v2.2.0): Loading from ca ~~~~省略〜〜〜 Discovered Package: laravel/tinker Discovered Package: nesbot/carbon Discovered Package: nunomaduro/collision Package manifest generated successfully. Application ready! Build something amazing.# マイグレーションファイルの作成 # --create=tasks はオプション。テーブルの名前を指定できる。 satou$ php artisan make:migration create_tasks_table --create=tasks Created Migration: 2019_03_05_052059_create_tasks_table# マイグレート実行、テーブルの作成 $php artisan migrate# モデルの作成 $ php artisan make:model Task Model created successfully.
- 投稿日:2019-03-05T17:48:21+09:00
初心者のLaravel5.8、Herokuデプロイ備忘録
#gitリポジトリの作成 git init#githubリモートリポジトリの追加 git remote add origin https://github.com/satou-yuuki/taskapp.git#githubへpush git push origin master#herokuのリモートリポジトリの追加? $ heroku git:remote -a taskapp-laravel set git remote heroku to https://git.heroku.com/taskapp-laravel.git#リモートリポジトリの確認 $ git remote heroku origin#herokuへpush $ git push heroku master#herokuアドオン postgreSQLの追加 無料版 $ heroku addons:create heroku-postgresql:hobby-dev#herokuの環境変数確認 $ heroku config === taskapp-laravel Config Vars APP_KEY: base64:euuylwlZCqo3Ae9z32Le0gZcQO3653b+E1mABIQMuTs= DATABASE_URL: postgres://sgahvfpoxsthtn:1680d670e04921e66f1047536b43b9c1b8464400fb7c8397b702c38b9dd99fad@ec2-54-83-196-179.compute-1.amazonaws.com:5432/d6etanlh202i2t satounoMacBook-Pro:taskapp satou$ heroku run php artisan migrate#環境変数の登録 $ heroku config:set DB_HOST=ec2-54-83-196-179.compute-1.amazonaws.com Setting DB_HOST and restarting ⬢ taskapp-laravel... done, v17 DB_HOST: ec2-54-83-196-179.compute-1.amazonaws.com $ heroku config:set DB_USERNAME=sgahvfpoxsthtn Setting DB_USERNAME and restarting ⬢ taskapp-laravel... done, v18 DB_USERNAME: sgahvfpoxsthtn $ heroku config:set DB_PASSWORD=1680d670e04921e66f1047536b43b9c1b8464400fb7c8397b702c38b9dd99fad Setting DB_PASSWORD and restarting ⬢ taskapp-laravel... done, v19 DB_PASSWORD: 1680d670e04921e66f1047536b43b9c1b8464400fb7c8397b702c38b9dd99fad $ heroku config:set DB_DATABASE=d6etanlh202i2t Setting DB_DATABASE and restarting ⬢ taskapp-laravel... done, v20 DB_DATABASE: d6etanlh202i2t#migrarationsテーブルの作成らしい。 必要なかったかも。 $ heroku run "php artisan migrate:install" Running php artisan migrate:install on ⬢ taskapp-laravel... up, run.3082 (Free)#マイグレート実行 マイグレーションファイルがマイグレートされる $ heroku run "php artisan migrate" Running php artisan migrate on ⬢ taskapp-laravel... up, run.9664 (Free) ************************************** * Application In Production! * ************************************** Do you really wish to run this command? (yes/no) [no]: > yse //yseでも通るってことはyだけでokぽい Migrating: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_000000_create_users_table Migrating: 2014_10_12_100000_create_password_resets_table Migrated: 2014_10_12_100000_create_password_resets_table Migrating: 2019_03_05_052059_create_tasks_table Migrated: 2019_03_05_052059_create_tasks_table#ブラウザ立ち上がってアプリが開く $heroku open
- 投稿日:2019-03-05T17:48:21+09:00
[初心者]Laravel5.8、Herokuデプロイ備忘録
#gitリポジトリの作成 git init#githubリモートリポジトリの追加 git remote add origin https://github.com/satou-yuuki/taskapp.git#githubへpush git push origin master#herokuのリモートリポジトリの追加? $ heroku git:remote -a taskapp-laravel set git remote heroku to https://git.heroku.com/taskapp-laravel.git#リモートリポジトリの確認 $ git remote heroku origin#herokuへpush $ git push heroku master#herokuアドオン postgreSQLの追加 無料版 $ heroku addons:create heroku-postgresql:hobby-dev#herokuの環境変数確認 $ heroku config === taskapp-laravel Config Vars APP_KEY: base64:euuylwlZCqo3Ae9z32Le0gZcQO3653b+E1mABIQMuTs= DATABASE_URL: postgres://sgahvfpoxsthtn:1680d670e04921e66f1047536b43b9c1b8464400fb7c8397b702c38b9dd99fad@ec2-54-83-196-179.compute-1.amazonaws.com:5432/d6etanlh202i2t satounoMacBook-Pro:taskapp satou$ heroku run php artisan migrate#環境変数の登録 $ heroku config:set DB_HOST=ec2-54-83-196-179.compute-1.amazonaws.com Setting DB_HOST and restarting ⬢ taskapp-laravel... done, v17 DB_HOST: ec2-54-83-196-179.compute-1.amazonaws.com $ heroku config:set DB_USERNAME=sgahvfpoxsthtn Setting DB_USERNAME and restarting ⬢ taskapp-laravel... done, v18 DB_USERNAME: sgahvfpoxsthtn $ heroku config:set DB_PASSWORD=1680d670e04921e66f1047536b43b9c1b8464400fb7c8397b702c38b9dd99fad Setting DB_PASSWORD and restarting ⬢ taskapp-laravel... done, v19 DB_PASSWORD: 1680d670e04921e66f1047536b43b9c1b8464400fb7c8397b702c38b9dd99fad $ heroku config:set DB_DATABASE=d6etanlh202i2t Setting DB_DATABASE and restarting ⬢ taskapp-laravel... done, v20 DB_DATABASE: d6etanlh202i2t#migrarationsテーブルの作成らしい。 必要なかったかも。 $ heroku run "php artisan migrate:install" Running php artisan migrate:install on ⬢ taskapp-laravel... up, run.3082 (Free)#マイグレート実行 マイグレーションファイルがマイグレートされる $ heroku run "php artisan migrate" Running php artisan migrate on ⬢ taskapp-laravel... up, run.9664 (Free) ************************************** * Application In Production! * ************************************** Do you really wish to run this command? (yes/no) [no]: > yse //yseでも通るってことはyだけでokぽい Migrating: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_000000_create_users_table Migrating: 2014_10_12_100000_create_password_resets_table Migrated: 2014_10_12_100000_create_password_resets_table Migrating: 2019_03_05_052059_create_tasks_table Migrated: 2019_03_05_052059_create_tasks_table#ブラウザ立ち上がってアプリが開く $heroku open
- 投稿日:2019-03-05T16:40:20+09:00
chart.jsで積み上げ横棒グラフを描く
はじめに
項目数が多いから縦グラフじゃなくて横グラフで描きたいってときありますよね。
方法
1.データの用意
データがなくちゃ始まらない。
$datas = array( '太郎' => array ( '国語' => 81 , '数学' => 79 , '理科' => 66 , '社会' => 72 , '英語' => 89 ), '次郎' => array ( '国語' => 60 , '数学' => 68 , '理科' => 50 , '社会' => 49 , '英語' => 91 ), '三郎' => array ( '国語' => 81 , '数学' => 65 , '理科' => 98 , '社会' => 81 , '英語' => 66 ) );2.データのこねくり回し
chart.jsに渡す用のデータを作成します。
//変数の初期化 $labels = ''; $japanese = ''; $math = ''; $science = ''; $social = ''; $english = ''; //データを文字列に foreach ($datas as $name => $values){ $labels = sprintf('%s"%s", ', $labels, $name); $japanese = sprintf('%s%s, ', $japanese, $values['国語']); $math = sprintf('%s%s, ', $math, $values['数学']); $science = sprintf('%s%s, ', $science, $values['理科']); $social = sprintf('%s%s, ', $social, $values['社会']); $english = sprintf('%s%s, ', $english, $values['英語']); } //いらない部分を削除 $labels = mb_substr($labels, 0, -2); $japanese = mb_substr($japanese, 0, -2); $math = mb_substr($math, 0, -2); $science = mb_substr($science, 0, -2); $social = mb_substr($social, 0, -2); $english = mb_substr($english, 0, -2);中身は、
labels = "太郎", "次郎", "三郎"
math = 81, 60, 81
こんな感じの文字列が入っています。もうちょっと綺麗にしたいですね♨
3.HTML
<!DOCTYPE html> <html> <head> <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.js"></script> <title>得点グラフ</title> </head> <body> <div class = "graph_box"> <canvas id="score" class = "chart"></canvas> </div> </body> </html>canvasタグの間にグラフが入ります。
4.chart.js
<script> //ロス回数 var ctx = document.getElementById("score"); var myChartSt = new Chart(ctx, { type: "horizontalBar" //barで縦グラフ , data: { labels: [<?php echo $labels;?>] , datasets: [ { label: '国語' , borderWidth: 1 , backgroundColor: "red" , borderColor: "red" , data: [<?php echo $japanese;?>] } , { label: '数学' , borderWidth: 1 , backgroundColor: 'orange' , borderColor: 'orange' , data: [<?php echo $math;?>] } , { label: '理科' , borderWidth: 1 , backgroundColor: 'green' , borderColor: 'green' , data: [<?php echo $science;?>] } , { label: '社会' , borderWidth: 1 , backgroundColor: 'skyblue' , borderColor: 'skyblue' , data: [<?php echo $social;?>] } ,{ label: '英語' , borderWidth: 1 , backgroundColor: 'black' , borderColor: 'black' , data: [<?php echo $english;?>] } ] } , options: { title: { display: true , text: 'みんなの点数' //グラフの見出し , padding: 3 , fontSize: 20 } , scales: { yAxes: [ { stacked: true //積み上げ棒グラフの設定 , xbarThickness: 16 //棒グラフの幅 , scaleLabel: { // 軸ラベル display: true // 表示設定 , labelString: '名前' // ラベル , fontSize: 16 // フォントサイズ } } ] , xAxes: [ { stacked: true //積み上げ棒グラフにする設定 , scaleLabel: { // 軸ラベル display: false // 表示設定 , labelString: '総得点' // ラベル , fontSize: 16 // フォントサイズ } } ] } , legend: { labels: { boxWidth:30 , padding:20 //凡例の各要素間の距離 } , display: true } , tooltips: { mode: "label" } } }); </script>積み立て横棒グラフにするには、
・type: "horizontalBar"
・yAxes:[{stacked: true}]
とする。結果
まとめ
縦グラフのときと、x,yが逆になるので注意。
下に全ソースを貼っておきます
aaa.php<?php $datas = array( '太郎' => array ( '国語' => 81 , '数学' => 79 , '理科' => 66 , '社会' => 72 , '英語' => 89 ), '次郎' => array ( '国語' => 60 , '数学' => 68 , '理科' => 50 , '社会' => 49 , '英語' => 91 ), '三郎' => array ( '国語' => 81 , '数学' => 65 , '理科' => 98 , '社会' => 81 , '英語' => 66 ) ); //変数の初期化 $labels = ''; $japanese = ''; $math = ''; $science = ''; $social = ''; $english = ''; //データを文字列に foreach ($datas as $name => $values){ $labels = sprintf('%s"%s", ', $labels, $name); $japanese = sprintf('%s%s, ', $japanese, $values['国語']); $math = sprintf('%s%s, ', $math, $values['数学']); $science = sprintf('%s%s, ', $science, $values['理科']); $social = sprintf('%s%s, ', $social, $values['社会']); $english = sprintf('%s%s, ', $english, $values['英語']); } //いらない部分を削除 $labels = mb_substr($labels, 0, -2); $japanese = mb_substr($japanese, 0, -2); $math = mb_substr($math, 0, -2); $science = mb_substr($science, 0, -2); $social = mb_substr($social, 0, -2); $english = mb_substr($english, 0, -2); ?> <!DOCTYPE html> <html> <head> <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.js"></script> <title>得点グラフ</title> </head> <body> <div class = "graph_box"> <canvas id="score" class = "chart"></canvas> </div> </body> </html> <script> //ロス回数 var ctx = document.getElementById("score"); var myChartSt = new Chart(ctx, { type: "horizontalBar" //barで縦グラフ , data: { labels: [<?php echo $labels;?>] , datasets: [ { label: '国語' , borderWidth: 1 , backgroundColor: "red" , borderColor: "red" , data: [<?php echo $japanese;?>] } , { label: '数学' , borderWidth: 1 , backgroundColor: 'orange' , borderColor: 'orange' , data: [<?php echo $math;?>] } , { label: '理科' , borderWidth: 1 , backgroundColor: 'green' , borderColor: 'green' , data: [<?php echo $science;?>] } , { label: '社会' , borderWidth: 1 , backgroundColor: 'skyblue' , borderColor: 'skyblue' , data: [<?php echo $social;?>] } ,{ label: '英語' , borderWidth: 1 , backgroundColor: 'black' , borderColor: 'black' , data: [<?php echo $english;?>] } ] } , options: { title: { display: true , text: 'みんなの点数' //グラフの見出し , padding: 3 , fontSize: 20 } , scales: { yAxes: [ { stacked: true //積み上げ棒グラフの設定 , xbarThickness: 16 //棒グラフの幅 , scaleLabel: { // 軸ラベル display: true // 表示設定 , labelString: '名前' // ラベル , fontSize: 16 // フォントサイズ } } ] , xAxes: [ { stacked: true //積み上げ棒グラフにする設定 , scaleLabel: { // 軸ラベル display: false // 表示設定 , labelString: '総得点' // ラベル , fontSize: 16 // フォントサイズ } } ] } , legend: { labels: { boxWidth:30 , padding:20 //凡例の各要素間の距離 } , display: true } , tooltips: { mode: "label" } } }); </script>あとがき
案外まとめられてる記事がなかったので、備忘録です。
というか、これ使いやすいし、わかりやすいからそんなにまとめる必要もないかもしれない。
- 投稿日:2019-03-05T11:50:01+09:00
LaravelのEloquentでカラムの変更情報を取得
カラムが変更されたか判断する必要があり、動作を確認したメモ。
>>> $group = App\Group::all()->last(); => App\Group {#49052 id: 10, name: "Aグループ", mailaddress: "hoge@example.com", prefectures_id: 13, city: "千代田区", address: "大手町一丁目", telephone: "03XXXXXXXX", created_at: "2019-01-09 12:09:56", updated_at: "2019-01-09 12:09:56", }値を変更してみる
>>> $group->name = "hoge"; => "hoge" >>> $group->telephone = "03YYYYYYYY" => "03YYYYYYYY"
isDrity
メソッドで確認。変更されてるのでtrue
ですね。>>> $group->isDirty(); => trueカラム絞っても確認できます。
>>> $group->isDirty("name"); => true >>> $group->isDirty("city"); => false >>> $group->isDirty(["name", "city"]); => true変更された内容は
getDirty
メソッドで確認。
変更されたnameとtelephoneの内容が確認できる。>>> $group->getDirty(); => [ "name" => "hoge", "telephone" => "03YYYYYYYY", ]変更前の値が知りたい場合には
getOriginal
メソッド>>> $group->getOriginal(); => [ "id" => 10, "name" => "Aグループ", "mailaddress" => "hoge@example.com", "prefectures_id" => 13, "city" => "千代田区", "address" => "大手町一丁目", "telephone" => "03XXXXXXXX", "created_at" => "2019-01-09 12:09:56", "updated_at" => "2019-01-09 12:09:56", ] >>> $group->getOriginal("name"); => "Aグループ"
save
してみる。
isDirty
はfalseを返すようになり、getOriginal
で確認しても更新後の値に書き換わっている。>>> $group->save(); => true >>> $group->isDirty(); => false >>> $group->getOriginal("name"); => "hoge"
- 投稿日:2019-03-05T09:57:55+09:00
【baserCMS】お問い合わせページの分岐
お問い合わせページのスラッグとして「contact」を使うことが多いと思うんですが、
お問い合わせページだけを分岐する為に、固定ページの分岐と同じように<?php if (strtolower($this->BcBaser->getContentsName()) === 'contact'): ?> 〜contactページだけに表示したいコンテンツ〜 <?php endif; ?>このように書いても効きません。
フォームページを分岐したいときは$currentContent = $this->BcBaser->getCurrentContent(); // 現在のコンテンツ情報を取得 var_dump($currentContent);を使ってname値、plugin値、type値を用いて分岐させた方が、
フォームのURLが変わったときにも対応できるのでおすすめです。書き方としては
<?php $currentContent = $this->BcBaser->getCurrentContent(); if($currentContent['name'] === "contact"){ echo 'contactページだけに表示したいコンテンツ'; }; ?>こんな感じ。
逆にcontact以外のページに表記したいときは<?php $currentContent = $this->BcBaser->getCurrentContent(); if($currentContent['name'] !== "contact"){ echo 'contactページ以外に表示したいコンテンツ'; }; ?>という感じになります。
- 投稿日:2019-03-05T08:50:58+09:00
php-master-changes 2019-03-04
今日は不要コードの削除、テストの並列実行対策、テストの単純化、ビルドシステムのリファクタリング、parent:: の検証をコンパイル時に行う修正のリバート、opcache のバグ修正、一部テストの Windows 対応、Travis ビルドのオプション修正があった!
2019-03-04
petk: [ci skip] Remove oniguruma and libzip leftovers
- https://github.com/php/php-src/commit/cb77d9c3cc1deb36cd18c661ae37e7a6c712449b
- [7.4~]
- バンドルされなくなった鬼車、libzip の名残を削除
nikic: Use separate directories for win32 dir tests
- https://github.com/php/php-src/commit/15711a4db0805999ee1e372b96f909b3db1a0b1e
- [7.4~]
- win32 のディレクトリ操作系のテストで、別々の一時ディレクトリを作るよう修正
- 並列実行対策
nikic: Use separate output files in x2gd tests
- https://github.com/php/php-src/commit/e27ba75e0f2929cc2ecbbb45761eec60fd9abffb
- [7.4~]
- ext/gd で、ホニャララ2gd() のテストで別々の一時ファイルを作るよう修正
- 並列実行対策
nikic: Don't generate script for proc_open_pipes tests
- https://github.com/php/php-src/commit/6be93b29ebc54ffcfd3ed689885c7040609a38aa
- [7.4~]
- proc_open() のテストで、sleep() するだけのスクリプトを生成するスクリプトが謎にあったが、実行時に生成する意味なくね?と、生成後のスクリプトを直接リポジトリに入れて使うよう修正
petk: Remove mkinstalldirs, install-sh and missing
- https://github.com/php/php-src/commit/1c32d751c7cb2abf26b1589f26c80558597e489b
- [7.4~]
- ビルドシステムで、不要に空ファイルを生成していた mkinstalldirs、install-sh、missing に関する記述を削除
- Automake 用のファイルだったが、今現在使われていない
petk: Replace PHP_TM_GMTOFF with AC_CHECK_MEMBERS
- https://github.com/php/php-src/commit/a8c3e22d231a9993c253d446ce23425662ac1645
- [7.4~]
- ビルドシステムで、PHP_TM_GMTOFF を autoconf の AC_CHECK_MEMBERS で置き換え
nikic: Revert "Detect invalid uses of parent:: during compilation"
- https://github.com/php/php-src/commit/deb44d405eb27a6654ad9a57c1e5f641218b22a4
- [7.4~]
- 先日の修正をリバート
- Mockery が親の有無によらず
parent::
を使っていて、少なくとも 7.4 で BC 崩すほどじゃないや、となったらしいpetk: Move Makefile.global and Makefile.gcov to build directory
- https://github.com/php/php-src/commit/2957651c5c2a003e96318fc9217764b91266723a
- [7.4~]
- Makefile.global と Makefile.gcov を build/ 下へ移動
nikic: Fixed bug #77691
- https://github.com/php/php-src/commit/3f00c9367d080bfa6153d6c7673f3ca4fad73c31
- [7.2~]
- ext/opcache で、関数呼び出し時の引数指定において配列への代入を行うと、渡される引数が代入された値でなく代入された配列となる問題の修正
- あ、正しくはそうなるんだこれという感じもありつつ、添え字指定してる場合を考えるとですよねみたいな
weltling: Fix test portability
- https://github.com/php/php-src/commit/040196972c7a757200c2e1aa88f360322c38ffde
- [7.2~]
- テストの移植性向上
- やってるの weltling の人だし、たぶん Windows 対応なんだけど、ディレクトリセパレータっぽい %e ってここだと何の奴だ、と思ったら run-tests.php の奴か
- このへん str_replace() で自前置き換えなんだなー
Fabien Villepinte: Replace --with-gd by --enable-gd for Travis
- https://github.com/php/php-src/commit/972a65c882b09e92708020ea278d133bbcb0d98a
- [7.4~]
- Travis で --with-gd を --enable-gd で置き換え
- 投稿日:2019-03-05T07:46:07+09:00
コーディング規約って、やっぱり必要だよね
外部に開発を依頼する現場。
納品後の品質にバラつきが発生することはよくあることですよね。
一定品質を確保する上でコーディング規約はやっぱり大切だと実感している今日この頃。今回はコーディング規約を決める際に必要なことを改めて考えてみました。
コーディング規約を決めるのに必要なことは?
プロジェクトの規模やメンバーのスキル、使用言語によって変わってきますが、
あらかじめ以下の定義をしておくことが重要になります。スコープ(適用する範囲)
内部で使うだけか、外部依頼時にも使うものかのスコープを決めておく。
品質保持の手段
コーディング規約を設定しただけでは品質や生産性は上がりません。例えばプロジェクトチームに、不慣れなメンバーや新人が居た場合に、経験者が周知、レビューする必要性があります。
またCIツールで自動で規約チェックしてくれるような仕掛けも重要になってきます。
例: PHPの場合
PHP コードのコーディング規約をチェックすることにしたメンテナンス方法
プロジェクト、メンバーに応じて規約のメンテナンスを行う必要があります。品質管理の担当などを組織内で予め立てておくと良いでしょう。
PHPでのコーディング規約
今回はPHPでオーソドックスな規約であるPSRのうちPSR1、PSR2をベースにサンプルを作ってみました。
外部に仕事をお願いする際に明示することで、納品後のトラブルを予防することができます。インデントについて
スペースのみにすることで、差分表示やパッチ、履歴や注釈がずれる問題を回避できます。スペース4つ、タブは禁止改行コードについて
LF開始タグについて
<?php <?=終了タグについて
?>は省略文字コードについて
UTF-8(BOM無し)コメントについて
// コメントは開始後スペースを1文字開けますプログラム全体、クラスのコメントについて
/** * ××を実現するクラス // 説明 * // return値、型 * @return string JSON */クラス名について
StudlyCaps(単語の先頭文字を大文字で表記する記法)記法メソッド名について
camelCase記法require、include、ini、エラーや例外発行、グローバル変数や静的変数修正、ファイルからの読み込み・書き込み処理について
<?php // 同じファイル内でfunctionを定義すると振る舞いが変わってしまう可能性があるのでファイル単位で分ける。 ini_set('error_reporting', E_ALL); include "file.php"; echo "<html>\n"; // NG function foo() { }namaspaceについて
<?php // namespaceの後は一行改行する namespace Vendor\Model; class Foo { }定数について
<?php namespace Vendor\Model; class Foo { // アンダースコア文字を区切り文字として大文字で定義する const VERSION = '1.0'; const DATE_APPROVED = '2012-06-01'; }行の長さ
ハードリミットはなし ソフトリミットは120文字上限行末
行末の空白文字列は禁止ファイル末
ファイルの最後に空行を入れる予約語、true、false、nullについて
大文字禁止、小文字のみ名前空間とuse演算子による定義について
<?php // use定義は名前空間宣言の後でなければなりません。 // 名前空間の定義の後に空行が必要です。 namespace Vendor\Package; use FooClass; use BarClass as Bar; use OtherVendor\OtherPackage\BazClass; // use定義のブロックの後には空行が必要です。extendsとimplementsについて
<?php namespace Vendor\Package; use FooClass; use BarClass as Bar; use OtherVendor\OtherPackage\BazClass; // クラス名と同じ行で定義する。 class ClassName extends ParentClass implements \ArrayAccess, \Countable { }implementsについて
<?php namespace Vendor\Package; use FooClass; use BarClass as Bar; use OtherVendor\OtherPackage\BazClass; // インデントにより揃えることで、複数行に分割しても構いません。 class ClassName extends ParentClass implements \ArrayAccess, \Countable, \Serializable { }アクセス修飾子について
<?php namespace Vendor\Package; class ClassName { // プロパティ定義にvarは禁止 // ステートメントあたりプロパティ定義は一つ // プロパティ名にprotectedまたはprivateを示すためのアンダースコアは禁止 public $foo = null; }メソッドについて
<?php namespace Vendor\Package; class ClassName { // アクセス修飾子は、全てのメソッドに定義しなければなりません。 // protectedまたはprivateを示すためのアンダースコアは禁止 public function fooBarBaz($arg1, &$arg2, $arg3 = []) { } }メソッドの引数について
<?php namespace Vendor\Package; class ClassName { // 各カンマの後ろには1スペース // デフォルト値を持つ引数は、引数リストの最後に public function foo($arg1, &$arg2, $arg3 = []) { } }複数の引数はについて
<?php namespace Vendor\Package; class ClassName { // インデントにより揃え、複数行に分割 public function aVeryLongMethodName( ClassTypeHint $arg1, &$arg2, array $arg3 = [] ) { } }abstract、 final、 and staticについて
<?php namespace Vendor\Package; abstract class ClassName { protected static $foo; // abstractとfinalはアクセス修飾子の前に abstract protected function zim(); // staticはアクセス修飾子の後に定義 final public static function bar() { } }メソッド、関数の呼び出しについて
<?php bar(); $foo->bar($arg1); Foo::bar($arg2, $arg3); // 各カンマの後に1スペースを入れるメソッド、関数の複数引数について
<?php // インデントにより揃え、複数行に分割する。 $foo->bar( $longArgument, $longerArgument, $muchLongerArgument );if, elseif, else文について
<?php if ($expr1) { // eleseifは間にスペースを空けない } elseif ($expr2) { } else { }switch, case文について
<?php switch ($expr) { case 0: echo 'First case, with a break'; break; // no breakを明示的にコメントする case 1: echo 'Second case, which falls through'; // no break case 2: case 3: case 4: echo 'Third case, return instead of break'; return; default: echo 'Default case'; break; }while、 do while文
<?php while ($expr) { }do while文
<?php do { } while ($expr);for文
<?php for ($i = 0; $i < 10; $i++) { }foreach文
<?php foreach ($iterable as $key => $value) { }try、catch文
<?php try { } catch (FirstExceptionType $e) { } catch (OtherExceptionType $e) { }まとめ
PSR1、PSR2をベースに基本的なコーディング規約をまとめてみました。
前に触れたように規約を決めただけでは品質は上がりません。
開発者に周知することと、自動チェックできるインフラを整えることが重要です。
内部、外部関わらずコーディング規約を定義することからプログラムの品質をあげましょう。
- 投稿日:2019-03-05T07:46:07+09:00
コーディング規約を決める上で必要なことを考える
外部に開発を依頼する現場において、納品後の品質のバラつきが発生することはよくあることです。
外部に開発を依頼する前に一定品質を確保する上る上でコーディング規約に必要なことを改めて考えてみました。コーディング規約を決めるのに必要なことは?
プロジェクトの規模やメンバーのスキル、使用言語によって変わってきますが、
あらかじめ以下の定義をしておくことが重要になります。スコープ(適用する範囲)
内部で使うだけか、外部依頼時にも使うものかのスコープを決めておく。
品質保持の手段
コーディング規約を設定しただけでは品質や生産性は上がりません。例えばプロジェクトチームに、不慣れなメンバーや新人が居た場合に、経験者が周知、レビューする必要性があります。
またCIツールで自動で規約チェックしてくれるような仕掛けも重要になってきます。
例: PHPの場合
PHP コードのコーディング規約をチェックすることにしたメンテナンス方法
プロジェクト、メンバーに応じて規約のメンテナンスを行う必要があります。品質管理の担当などを組織内で予め立てておくと良いでしょう。
PHPでのコーディング規約
今回はPHPでオーソドックスな規約であるPSRのうちPSR1、PSR2をベースにサンプルを作ってみました。
外部に仕事をお願いする際に明示することで、納品後のトラブルを予防することができます。インデントについて
スペースのみにすることで、差分表示やパッチ、履歴や注釈がずれる問題を回避できます。スペース4つ、タブは禁止改行コードについて
LF開始タグについて
<?php <?=終了タグについて
?>は省略文字コードについて
UTF-8(BOM無し)コメントについて
// コメントは開始後スペースを1文字開けますプログラム全体、クラスのコメントについて
/** * ××を実現するクラス // 説明 * // return値、型 * @return string JSON */クラス名について
StudlyCaps(単語の先頭文字を大文字で表記する記法)記法メソッド名について
camelCase記法require、include、ini、エラーや例外発行、グローバル変数や静的変数修正、ファイルからの読み込み・書き込み処理について
<?php // 同じファイル内でfunctionを定義すると振る舞いが変わってしまう可能性があるのでファイル単位で分ける。 ini_set('error_reporting', E_ALL); include "file.php"; echo "<html>\n"; // NG function foo() { }namaspaceについて
<?php // namespaceの後は一行改行する namespace Vendor\Model; class Foo { }定数について
<?php namespace Vendor\Model; class Foo { // アンダースコア文字を区切り文字として大文字で定義する const VERSION = '1.0'; const DATE_APPROVED = '2012-06-01'; }行の長さ
ハードリミットはなし ソフトリミットは120文字上限行末
行末の空白文字列は禁止ファイル末
ファイルの最後に空行を入れる予約語、true、false、nullについて
大文字禁止、小文字のみ名前空間とuse演算子による定義について
<?php // use定義は名前空間宣言の後でなければなりません。 // 名前空間の定義の後に空行が必要です。 namespace Vendor\Package; use FooClass; use BarClass as Bar; use OtherVendor\OtherPackage\BazClass; // use定義のブロックの後には空行が必要です。extendsとimplementsについて
<?php namespace Vendor\Package; use FooClass; use BarClass as Bar; use OtherVendor\OtherPackage\BazClass; // クラス名と同じ行で定義する。 class ClassName extends ParentClass implements \ArrayAccess, \Countable { }implementsについて
<?php namespace Vendor\Package; use FooClass; use BarClass as Bar; use OtherVendor\OtherPackage\BazClass; // インデントにより揃えることで、複数行に分割しても構いません。 class ClassName extends ParentClass implements \ArrayAccess, \Countable, \Serializable { }アクセス修飾子について
<?php namespace Vendor\Package; class ClassName { // プロパティ定義にvarは禁止 // ステートメントあたりプロパティ定義は一つ // プロパティ名にprotectedまたはprivateを示すためのアンダースコアは禁止 public $foo = null; }メソッドについて
<?php namespace Vendor\Package; class ClassName { // アクセス修飾子は、全てのメソッドに定義しなければなりません。 // protectedまたはprivateを示すためのアンダースコアは禁止 public function fooBarBaz($arg1, &$arg2, $arg3 = []) { } }メソッドの引数について
<?php namespace Vendor\Package; class ClassName { // 各カンマの後ろには1スペース // デフォルト値を持つ引数は、引数リストの最後に public function foo($arg1, &$arg2, $arg3 = []) { } }複数の引数はについて
<?php namespace Vendor\Package; class ClassName { // インデントにより揃え、複数行に分割 public function aVeryLongMethodName( ClassTypeHint $arg1, &$arg2, array $arg3 = [] ) { } }abstract、 final、 and staticについて
<?php namespace Vendor\Package; abstract class ClassName { protected static $foo; // abstractとfinalはアクセス修飾子の前に abstract protected function zim(); // staticはアクセス修飾子の後に定義 final public static function bar() { } }メソッド、関数の呼び出しについて
<?php bar(); $foo->bar($arg1); Foo::bar($arg2, $arg3); // 各カンマの後に1スペースを入れるメソッド、関数の複数引数について
<?php // インデントにより揃え、複数行に分割する。 $foo->bar( $longArgument, $longerArgument, $muchLongerArgument );if, elseif, else文について
<?php if ($expr1) { // eleseifは間にスペースを空けない } elseif ($expr2) { } else { }switch, case文について
<?php switch ($expr) { case 0: echo 'First case, with a break'; break; // no breakを明示的にコメントする case 1: echo 'Second case, which falls through'; // no break case 2: case 3: case 4: echo 'Third case, return instead of break'; return; default: echo 'Default case'; break; }while、 do while文
<?php while ($expr) { }do while文
<?php do { } while ($expr);for文
<?php for ($i = 0; $i < 10; $i++) { }foreach文
<?php foreach ($iterable as $key => $value) { }try、catch文
<?php try { } catch (FirstExceptionType $e) { } catch (OtherExceptionType $e) { }まとめ
PSR1、PSR2をベースに基本的なコーディング規約をまとめてみました。
前に触れたように規約を決めただけでは品質は上がりません。
開発者に周知することと、自動チェックできるインフラを整えることが重要です。
内部、外部関わらずコーディング規約を定義することからプログラムの品質をあげましょう。
- 投稿日:2019-03-05T03:02:29+09:00
Laravel で最低限書いてあると嬉しいテスト
TLDR;
簡単なWEBアプリケーションを書くときに最低限書いておいて良かったと思うテストの紹介
リポジトリは下記
https://github.com/katsuren/laravel-test前提
Not SPA - JS が絡むものは NodeJS のテストフレームワーク使ったほうがよい
Not SPA でも JS が多い場合は dusk や codeception を使ったほうが良いと思う
Laravel についての知識、下記は知っているものとして解説する
factory、リレーションあたり
feature/unit テストについてサンプルアプリケーションの概要
CDの管理アプリケーション作る
アーティストとそのアルバム、曲名を管理できるアプリケーション
簡単のためにログインは省くテスト戦略的なもの
モデルが利用可能なこと、リレーションが正しいことをおさえる
基本的なリソースの CRUD のテストをおさえる
具体的には、各ルーティングがエラーで落ちていないことを確認する
また、各機能画面にてあるべき input があることを確認する (nameで確認)その他、このテストでは RefreshDatabase を利用するので、.env.testing を用意して別DBでテストしたほうが良い。
さもなくばテストのたびにDBの中身がクリアされる。M(モデル)のテスト
・マイグレーションを書く(テーブル定義)
・ファクトリーを書く(レコード生成の定義)
・ファクトリーが使えることを確認これで最低限モデルの定義が間違っていないことが保証される
具体的なファイルは下記のようになる
# database/migrations/2019_03_04_123520_create_artists_table.php <?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateArtistsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('artists', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('name'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('artists'); } }# app/Eloquents/Artist.php <?php namespace App\Eloquents; use Illuminate\Database\Eloquent\Model; class Artist extends Model { protected $guarded = [ 'id', ]; }# database/factories/ArtistFactory.php <?php use App\Eloquents\Artist; use Illuminate\Support\Str; use Faker\Generator as Faker; $factory->define(Artist::class, function (Faker $faker) { return [ 'name' => $faker->name, ]; });テストは下記
# tests/Unit/Eloquents/ArtistTest.php <?php namespace Tests\Unit\Eloquents; use App\Eloquents\Artist; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; class ArtistTest extends TestCase { use RefreshDatabase; public function testFactoryable() { $eloquent = app(Artist::class); $this->assertEmpty($eloquent->get()); // 初期状態では空であることを確認 $entity = factory(Artist::class)->create(); // 先程作ったファクトリーでレコード生成 $this->assertNotEmpty($eloquent->get()); // 再度getしたら中身が空ではないことを確認し、ファクトリ可能であることを保証 } }M(モデル)のテスト2(リレーション)
アーティストは複数のアルバムを保持することができ、
アルバムはアーティストに所属している、ことを確認する
これをテストすることで、リレーションが保証される。各モデルは下記のようになる
# app/Eloquents/Artist.php <?php class Artist extends Model { // 下記追加 public function albums() { return $this->hasMany(Album::class); } }# app/Eloquents/Album.php <?php namespace App\Eloquents; use Illuminate\Database\Eloquent\Model; class Album extends Model { protected $guarded = [ 'id', ]; public function artist() { return $this->belongsTo(Artist::class); } }テストは次のようになる
# tests/Unit/Eloquents/ArtistTest.php <?php namespace Tests\Unit\Eloquents; use App\Eloquents\Album; use App\Eloquents\Artist; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; class ArtistTest extends TestCase { // 下記のテスト追加 public function testArtistHasManyAlbums() { $count = 5; $artistEloquent = app(Artist::class); $albumEloquent = app(Album::class); $artist = factory(Artist::class)->create(); // アーティストを作成 $albums = factory(Album::class, $count)->create([ 'artist_id' => $artist->id, ]); // アーティストに紐づくアルバムレコードを作成 (create の引数に指定するとその値でデータ作成される) // refresh() で再度同じレコードを取得しなおし、リレーション先の件数が作成した件数と一致することを確認し、リレーションが問題ないことを保証 $this->assertEquals($count, count($artist->refresh()->albums)); } }# tests/Unit/Eloquents/AlbumTest.php <?php namespace Tests\Unit\Eloquents; use App\Eloquents\Album; use App\Eloquents\Artist; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; class AlbumTest extends TestCase { use RefreshDatabase; public function testFactoryable() { $eloquent = app(Album::class); $this->assertEmpty($eloquent->get()); $entity = factory(Album::class)->create(); $this->assertNotEmpty($eloquent->get()); } public function testAlbumBelongsToArtist() { $albumEloquent = app(Album::class); $artistEloquent = app(Artist::class); $artist = factory(Artist::class)->create(); $album = factory(Album::class)->create([ 'artist_id' => $artist->id, ]); $this->assertNotEmpty($album->artist); } }VC (ビュー・コントローラー)のテスト
具体的なソースコードは長くなってしまうので概要のリポジトリを参照のこと。
見た目は変わりやすいのでここではテスト対象にしない。(崩れやすいテスト) もし必要な場合は dusk などでスクリーンショット比較などをする。
簡単な CRUD コントローラーで、各ルーティングにアクセスして 4xx または 5xx が出ないことを確実にする。
リソースをそれぞれ簡単に説明するとindex = リソースの一覧表示の画面、検索フォームなどがある場合が多い
show = 指定のリソースの詳細表示画面、
create = 新規作成画面、フォームがあることを確認する
store = 新規作成処理(POST)
edit = リソースの更新設定画面、フォームがあることを確認する
update = 更新処理(PUT)
destroy = 削除処理(DELETE)となる。
各画面の機能として、input の name="xxx" がすべて表示されることを assert することで必ず input があるということを保証する。
これにより、最低限入力フォームの存在とその処理が保証されることになる。
例えば ArtistsController は下記のような実装だとする# app/Http/Controllers/ArtistsController.php <?php namespace App\Http\Controllers; use App\Eloquents\Artist; use App\Http\Requests\ArtistRequest; class ArtistsController extends Controller { protected $artistEloquent; public function __construct(Artist $artistEloquent) { $this->artistEloquent = $artistEloquent; } public function index() { $artists = $this->artistEloquent->pimp(request()->input('search', []))->get(); return view('artists.index')->with([ 'artists' => $artists, ]); } public function show($id) { $artist = $this->artistEloquent->find($id); return view('artists.show')->with([ 'artist' => $artist, ]); } public function create() { return view('artists.edit')->with([ 'isCreate' => true, ]); } public function store(ArtistRequest $request) { $artist = $this->artistEloquent->create($request->input('artist')); return redirect('/artists/' . $artist->id)->with('flash_message', 'アーティストを作成しました'); } public function edit($id) { $artist = $this->artistEloquent->find($id); return view('artists.edit')->with([ 'isCreate' => false, 'artist' => $artist, ]); } public function update(ArtistRequest $request, $id) { $artist = $this->artistEloquent->find($id); $artist->fill($request->input('artist')); $artist->save(); return redirect('/artists/' . $id)->with('flash_message', 'アーティストを更新しました'); } public function destroy($id) { $artist = $this->artistEloquent->find($id); $artist->delete(); return redirect('/artists')->with('flash_message', 'アーティストを削除しました'); } }それに対するテストは下記のように HTTP アクセスで確認する
基本的にHTMLの検査は文字列の検査しかできない、つまりdocumentの要素をリッチに検索するAPIは提供されておらず、その場合はduskを利用する。
簡素に変更がされないものを対象に検査する。具体的には下記の項目。
・200(HTTP OK)が返ること、またはリダイレクト(301 or 302)が返ること。
・フォームが存在する場合はそのアクションURLが存在すること、正しいこと。
・フォームが存在する場合はその項目(name=xxx)すべてが存在すること。# tests/Feature/ArtistsControllerTest.php <?php namespace Tests\Feature; use App\Eloquents\Artist; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Str; use Tests\TestCase; class ArtistsControllerTest extends TestCase { use RefreshDatabase; public function testCanSeeIndex() { $response = $this->get('/artists'); $response->assertOk() // インデックスにアクセスして 200 が返ることを確認 ->assertSee('action="/artists"') // 検索フォームのアクションがあることを確認 ->assertSee('name="search[name]"'); // search[name] という検索項目があることを確認 } public function testCanSeeShow() { $artist = factory(Artist::class)->create(); $response = $this->get('/artists/' . $artist->id); $response->assertOk() ->assertSee($artist->name); // 詳細ページでレコード固有の文字列が閲覧できることを確認 } public function testCanCreate() { $response = $this->get('/artists/create'); $response->assertOk() ->assertSee('action="/artists"') // 作成フォームがあること ->assertSee('name="artist[name]"'); // 名前入力フォームがあること $name = Str::random(10); $response = $this->from('/artists/create')->post('/artists', [ 'artist' => [ 'name' => $name, ], ]); // アクションURLに対してポストしてリダイレクトされること $response->assertRedirect(); $this->assertDatabaseHas('artists', ['name' => $name]); // DBにデータが作成されていることで新規作成処理を保証する } public function testCanUpdate() { $artist = factory(Artist::class)->create(); $response = $this->get('/artists/' . $artist->id . '/edit'); $response->assertOk() ->assertSee('action="/artists/' . $artist->id . '"') // 編集フォームがあること ->assertSee('name="artist[name]"'); // 名前入力フォームがあること $name = Str::random(10); $response = $this->from('/artists/' . $artist->id . '/edit')->put('/artists/' . $artist->id, [ 'artist' => [ 'name' => $name, ], ]); // アクションURLに対してポストしてリダイレクトされること $response->assertRedirect(); $this->assertDatabaseHas('artists', ['name' => $name]); // レコードに変更した名前のレコードがあることで編集処理を保証する } public function testCanDelete() { $artist = factory(Artist::class)->create(); $this->assertDatabaseHas('artists', ['id' => $artist->id]); // テスト前にレコードがDBに存在することを確認 $response = $this->from('/artists')->delete('/artists/' . $artist->id); $response->assertRedirect(); $this->assertDatabaseMissing('artists', ['id' => $artist->id]); // DELETEルートにアクセス後、DBからレコードがなくなっていることで削除処理を保証する } }最後にまとめ
上記のものではもちろんテストが十分というわけではないが、最低限書いてあると嬉しい。レビューもテスト書かれてないよりはあったほうがいい。
あと、実際のアプリケーションではもちろんこのように単純ではない場合が多いので、必要に応じて書き足す必要がある。というかこのサンプルでも色々足りていないのは承知している。
簡単に書いたつもりだが意外と時間かかった。
しかし、各モデルがしっかり使えるものであることの保証と、各ルーティングにアクセス可能であることが証明できていれば、多少手荒な更新があってもなんとかやっていけるはず。テストどう書いていいかわからない、方針がたたないような初学者の人の助けになればと思う。