20190305のPHPに関する記事は17件です。

独自オプションを作成して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が作成される

スクリーンショット 2019-03-05 16.18.43.png

  • getOptionParser

  • 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
  • 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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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"

  1. Java, Swift, Ruby, JavaScriptなど 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

python, ruby, php, node のループの速さ

今日も楽しいマイクロベンチマーク。

某所で python の for ループが遅いという話を聞いたので、そうなの? と思って他の言語と比べてみた。

ソース

python3

python3
import 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.js
const len = process.argv[2]|0;
let r=0;
for( let i=0 ; i<len ; ++i ){
  ++r;
}
console.log(r);

ruby

ruby2.6
n=ARGV[0].to_i
r=0
n.times do
  r+=1
end
p r

測る人

測る人はわりとやる気ない感じで、Benchmark.realtime を使っている。

bench.rb
require "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

各言語のファイル名がいい加減なのがバレるね。

結果

結果は下記グラフの通り。
両対数グラフ注意。

image.png

測る人のソースコードを見ると分かる通り、プログラムの起動時間を含んでいる。
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 だけ起動が速いらしい。そういうものか。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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項目でこのプロファイルを選択しておきます。
 2019-03-05 19.51.49.png

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: OK

rep2のセットアップは終わりました。これで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.php
phpinfo.php
<?php
    phpinfo();
?>

これをブラウザから見てみると
 2019-03-05 20.55.50.png
読み込まれてるじゃん!

結論

/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を使いたい人向けです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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ともにエラーハンドリングなどは全て省略されていますのでプロダクションとして使う場合はもう少し手を加えてあげる必要がありますのでご注意を!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初めて使った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ドライバをインストール
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 3

srcは解析対象のコードがあるディレクトリ、3は設定レベル。1〜8の値を渡す。1は最も厳密な解析を行う。8は最もゆるい。

解析を実行する:

./vendor/bin/psalm

Psalmの設定

Psalmはデフォルトの設定でもさまざまな問題を検出してくれるが、設定ファイルpsalm.xmlに設定を加えることで検査の厳密さを上げたり、振る舞いを変えたりすることができる。

<psalm />の設定

totallyTyped

totallyTypedtrueをセットすると、Psalmが型を推測できないコードをエラーとして扱うようになる。

psalm.xml
<psalm
  totallyTyped="true"
/>

strictBinaryOperands

文字列結合と数値の足し算を厳密にチェックする。つまり、文字列に対する+と、数値に対する.の使用をエラーとして扱うようになる。

psalm.xml
<psalm
  strictBinaryOperands="true"
>

allowPhpStormGenerics

PhpStormのために配列の型の情報をdocblockにArrayIterator|string[]などと書くことがあるが、allowPhpStormGenericstrueにするとその情報を尊重するようになる。

psalm.xml
<psalm
  allowPhpStormGenerics="true"
>

まとめ

本稿では、PHPのコード解析ツールについて紹介した。

  • PsalmはPHPコードを解析して問題点をあぶり出す。
  • 設定することでより厳格にチェックすることができる。

質問があればコメントください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[初心者]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.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者の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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[初心者]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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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}]
とする。

結果

キャプチャ.PNG

まとめ

縦グラフのときと、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>

あとがき

案外まとめられてる記事がなかったので、備忘録です。
というか、これ使いやすいし、わかりやすいからそんなにまとめる必要もないかもしれない。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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ページ以外に表示したいコンテンツ';
      }; ?>

という感じになります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

php-master-changes 2019-03-04

今日は不要コードの削除、テストの並列実行対策、テストの単純化、ビルドシステムのリファクタリング、parent:: の検証をコンパイル時に行う修正のリバート、opcache のバグ修正、一部テストの Windows 対応、Travis ビルドのオプション修正があった!

2019-03-04

petk: [ci skip] Remove oniguruma and libzip leftovers

nikic: Use separate directories for win32 dir tests

nikic: Use separate output files in x2gd tests

nikic: Don't generate script for proc_open_pipes tests

petk: Remove mkinstalldirs, install-sh and missing

petk: Replace PHP_TM_GMTOFF with AC_CHECK_MEMBERS

nikic: Revert "Detect invalid uses of parent:: during compilation"

petk: Move Makefile.global and Makefile.gcov to build directory

nikic: Fixed bug #77691

weltling: Fix test portability

Fabien Villepinte: Replace --with-gd by --enable-gd for Travis

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コーディング規約って、やっぱり必要だよね

外部に開発を依頼する現場。
納品後の品質にバラつきが発生することはよくあることですよね。
一定品質を確保する上でコーディング規約はやっぱり大切だと実感している今日この頃。

今回はコーディング規約を決める際に必要なことを改めて考えてみました。

コーディング規約を決めるのに必要なことは?

プロジェクトの規模やメンバーのスキル、使用言語によって変わってきますが、
あらかじめ以下の定義をしておくことが重要になります。

スコープ(適用する範囲)

内部で使うだけか、外部依頼時にも使うものかのスコープを決めておく。

品質保持の手段

コーディング規約を設定しただけでは品質や生産性は上がりません。例えばプロジェクトチームに、不慣れなメンバーや新人が居た場合に、経験者が周知、レビューする必要性があります。
また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をベースに基本的なコーディング規約をまとめてみました。
前に触れたように規約を決めただけでは品質は上がりません。
開発者に周知することと、自動チェックできるインフラを整えることが重要です。
内部、外部関わらずコーディング規約を定義することからプログラムの品質をあげましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コーディング規約を決める上で必要なことを考える

外部に開発を依頼する現場において、納品後の品質のバラつきが発生することはよくあることです。
外部に開発を依頼する前に一定品質を確保する上る上でコーディング規約に必要なことを改めて考えてみました。

コーディング規約を決めるのに必要なことは?

プロジェクトの規模やメンバーのスキル、使用言語によって変わってきますが、
あらかじめ以下の定義をしておくことが重要になります。

スコープ(適用する範囲)

内部で使うだけか、外部依頼時にも使うものかのスコープを決めておく。

品質保持の手段

コーディング規約を設定しただけでは品質や生産性は上がりません。例えばプロジェクトチームに、不慣れなメンバーや新人が居た場合に、経験者が周知、レビューする必要性があります。
また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をベースに基本的なコーディング規約をまとめてみました。
前に触れたように規約を決めただけでは品質は上がりません。
開発者に周知することと、自動チェックできるインフラを整えることが重要です。
内部、外部関わらずコーディング規約を定義することからプログラムの品質をあげましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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からレコードがなくなっていることで削除処理を保証する
    }
}

最後にまとめ

上記のものではもちろんテストが十分というわけではないが、最低限書いてあると嬉しい。レビューもテスト書かれてないよりはあったほうがいい。
あと、実際のアプリケーションではもちろんこのように単純ではない場合が多いので、必要に応じて書き足す必要がある。というかこのサンプルでも色々足りていないのは承知している。
簡単に書いたつもりだが意外と時間かかった。
しかし、各モデルがしっかり使えるものであることの保証と、各ルーティングにアクセス可能であることが証明できていれば、多少手荒な更新があってもなんとかやっていけるはず。テストどう書いていいかわからない、方針がたたないような初学者の人の助けになればと思う。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む