とりあえずmacにインストールしました。

とりあえずGetting Started のVideoに沿ってやるか。

とりあえず新規作成画面

なるほど、Cacooみたいなこともできるのね。
1920 x 1080だとちょっとWFとしては描きにくいから、カスタムサイズか。
チュートリアルだけだとモチベーションのフォーカスが難しいから、次実際にワイヤー描く時になったらXdでやってみるか。
OK、悪くない。
随机应变 ABCD: Always Be Coding and … : хороший
とりあえずmacにインストールしました。

とりあえずGetting Started のVideoに沿ってやるか。

とりあえず新規作成画面

なるほど、Cacooみたいなこともできるのね。
1920 x 1080だとちょっとWFとしては描きにくいから、カスタムサイズか。
チュートリアルだけだとモチベーションのフォーカスが難しいから、次実際にワイヤー描く時になったらXdでやってみるか。
OK、悪くない。
imgフォルダ内に画像が複数(数千枚)置いてあり、拡張子は不明だが、ファイル名だけ変数として持っており、該当のファイル名があればその画像を表示、無ければplaceholderの画像を表示したい。
まず画像を用意します。

拡張子分foreachで探してみたが、
$name = "cat";
$cfg = array('jpg', 'jpeg', 'png', 'gif');
foreach($cfg as $key){
if(file_exists("img/" .$name . "." . $key)){
echo "<img src='img/" . $name . "." .$key."' width='150px' height='100px'>";
}
}
これだと、拡張子の配列分回さないといけないのと、画像が無ければ、placeholderの表示ができない。
globとワイルドカードを使う
$name = "cat";
$result = glob("img/" . $name . ".*");
if(in_array($result)) {
echo '<img src="'.$result[0].'" width="150px" height="100px">';
} else {
echo '<img src="/img/150x150.png">';
}

OK!上等!
続いて、少々細かいことを調査していきます。
amazonの様に、住所を複数追加できる様にしたい。

<h1>住所追加</h1>
<div >
<form>
<button id="btn1" type="button" onclick="clickBtn1()">住所を追加する</button>
<div id="address1">
<input type="text" name="zipcode_1" value="" placeholder="郵便番号"><br>
<input type="text" name="address1_1" value="" placeholder="住所1"><br>
<input type="text" name="address2_1" value="" placeholder="住所2"><br>
</div>
</form>
</div>
<script>
document.getElementById("address1").style.display = "none";
function clickBtn1(){
const address1 = document.getElementById("address1");
if(address1.style.display=="block"){
document.getElementById("btn1").textContent = "住所を追加する";
document.getElementsByName("zipcode_1").value = "";
document.getElementsByName("address1_1").value = "";
document.getElementsByName("address2_1").value = "";
address1.style.display ="none";
} else{
document.getElementById("btn1").textContent = "追加しない";
address1.style.display ="block";
}
}
</script>

ここに更に住所を追加するボタンを加える。
– buttonではなく、spanタグにして少しスタイリングする
– 住所1を閉じる時は、住所2を初期化する必要がある
<h1>住所追加</h1>
<div >
<form>
<span id="btn1" onclick="clickBtn1()">住所を追加する</span>
<div id="address1">
<input type="text" name="zipcode_1" value="" placeholder="郵便番号"><br>
<input type="text" name="address1_1" value="" placeholder="住所1"><br>
<input type="text" name="address2_1" value="" placeholder="住所2"><br><br>
<span id="btn2" onclick="clickBtn2()">住所を追加する</span>
</div>
<div id="address2">
<input type="text" name="zipcode_2" value="" placeholder="郵便番号"><br>
<input type="text" name="address1_2" value="" placeholder="住所1"><br>
<input type="text" name="address2_2" value="" placeholder="住所2"><br><br>
</div>
</form>
</div>
<script>
document.getElementById("address1").style.display = "none";
document.getElementById("address2").style.display = "none";
function clickBtn1(){
const address1 = document.getElementById("address1");
if(address1.style.display=="block"){
// 住所1
document.getElementById("btn1").textContent = "住所を追加する";
document.getElementsByName("zipcode_1").value = "";
document.getElementsByName("address1_1").value = "";
document.getElementsByName("address2_1").value = "";
address1.style.display ="none";
// 住所2
document.getElementsByName("zipcode_2").value = "";
document.getElementsByName("address1_2").value = "";
document.getElementsByName("address2_2").value = "";
document.getElementById("address2").style.display = "none";
document.getElementById("btn2").textContent = "住所を追加する";
} else{
document.getElementById("btn1").textContent = "追加をやめる";
address1.style.display ="block";
}
}
function clickBtn2(){
const address2 = document.getElementById("address2");
if(address2.style.display=="block"){
document.getElementById("btn2").textContent = "住所を追加する";
document.getElementsByName("zipcode_2").value = "";
document.getElementsByName("address1_2").value = "";
document.getElementsByName("address2_2").value = "";
address2.style.display ="none";
} else{
document.getElementById("btn2").textContent = "追加をやめる";
address2.style.display ="block";
}
}
</script>

OK! これを実装する
### レコード数: 2001件
mysql> select count(id) from orders2;
+———–+
| count(id) |
+———–+
| 2001 |
+———–+
1 row in set (0.04 sec)
### sql文
mysql.sql
select * from orders2 where supplier_num = 1;
### カラム数28
$ time (cat mysql.sql | mysql -u root -p test > /dev/null)
Enter password:
real 0m3.099s // プログラムの呼び出しから終了までにかかった実時間
user 0m0.009s // プログラム自体の処理時間(秒)(ユーザCPU時間)
sys 0m0.012s // プログラムを処理するために、OSが処理をした時間
### カラム数48
$ time (cat mysql.sql | mysql -u root -p test > /dev/null)
Enter password:
real 0m3.162s
user 0m0.013s
sys 0m0.009s
### カラム数116
$ time (cat mysql.sql | mysql -u root -p test > /dev/null)
Enter password:
real 0m3.504s
user 0m0.011s
sys 0m0.011s
確かにちょっと遅くなってるけど、カラム数50ぐらいだと、本当に誤差だな
商用環境ではテーブル結合により絞り込み検索を行うので検索スピードは変わってくるのが、レコード数が数千件程度では、カラム数が100を超えていてもそこまで影響はなさそうだな。
1. DatabaseSeeder
./database/seeds/DatabaseSeeder.php
public function run()
{
$this->call(Orders1TableSeeder::class);
}
2. make seeder
$ php artisan make:seeder Orders1TableSeeder
use Illuminate\Support\Facades\DB;
public function run()
{
DB::table('orders1')->insert([
'user_id'=>rand(1,10),
'supplier_num'=>rand(1,10),
'supplier_id1'=>rand(1,10),
'supplier_id2'=>rand(1,10),
'supplier_id3'=>rand(1,10),
'supplier_subtotal1'=>rand(100,10000),
'supplier_subtotal2'=>rand(100,10000),
'supplier_subtotal3'=>rand(100,10000),
'total'=>rand(100,10000),
'product_num'=>rand(1,10),
'product_id1'=>rand(1,10),
'product_id2'=>rand(1,10),
'product_id3'=>rand(1,10),
'product_id4'=>rand(1,10),
'product_id5'=>rand(1,10),
'qty1'=>rand(1,10),
'qty2'=>rand(1,10),
'qty3'=>rand(1,10),
'qty4'=>rand(1,10),
'qty5'=>rand(1,10),
'subtotal1'=>rand(100,10000),
'subtotal2'=>rand(100,10000),
'subtotal3'=>rand(100,10000),
'subtotal4'=>rand(100,10000),
'subtotal5'=>rand(100,10000),
'detail'=>'',
]);
}
3. db:seed
$ php artisan db:seed
mysql> select * from orders1;
+—-+———+————–+————–+————–+————–+——————–+——————–+——————–+———+————-+————-+————-+————-+————-+————-+——+——+——+——+——+———–+———–+———–+———–+———–+——–+————+————+
| id | user_id | supplier_num | supplier_id1 | supplier_id2 | supplier_id3 | supplier_subtotal1 | supplier_subtotal2 | supplier_subtotal3 | total | product_num | product_id1 | product_id2 | product_id3 | product_id4 | product_id5 | qty1 | qty2 | qty3 | qty4 | qty5 | subtotal1 | subtotal2 | subtotal3 | subtotal4 | subtotal5 | detail | created_at | updated_at |
+—-+———+————–+————–+————–+————–+——————–+——————–+——————–+———+————-+————-+————-+————-+————-+————-+——+——+——+——+——+———–+———–+———–+———–+———–+——–+————+————+
| 1 | 6 | 6 | 6 | 9 | 9 | 6713.00 | 7337.00 | 6092.00 | 9497.00 | 6 | 10 | 2 | 10 | 2 | 2 | 10 | 5 | 5 | 6 | 6 | 4003.00 | 7581.00 | 1475.00 | 2006.00 | 4347.00 | | NULL | NULL |
+—-+———+————–+————–+————–+————–+——————–+——————–+——————–+———+————-+————-+————-+————-+————-+————-+——+——+——+——+——+———–+———–+———–+———–+———–+——–+————+————+
1 row in set (0.00 sec)
mysql> truncate table orders1;
4. seederで2000件データを挿入する
for文で2000件入れる
public function run()
{
for($i=0;$i<=2000; $i++){
DB::table('orders1')->insert([
// 省略
]);
}
}
$ php artisan db:seed
5. mysql文
mysql.sql
select * from orders1 where supplier_num = 1;
$ time (cat mysql.sql | mysql -u root -p test > /dev/null)
Enter password:
real 0m3.099s // プログラムの呼び出しから終了までにかかった実時間
user 0m0.009s // プログラム自体の処理時間(秒)(ユーザCPU時間)
sys 0m0.012s // プログラムを処理するために、OSが処理をした時間
OK
これはカラム数29だが、カラム数を50, 100と増やした場合に、実行時間がどれ位変化するかテストする。
うむ、そこそこテストっぽいことやってる。
まず適当にテーブルを作ります。
$ php artisan make:migration create_orders1_table
続いて適当にカラムを作ります。
-> カラム数29
2020_11_26_122330_create_orders1_table.php
public function up()
{
Schema::create('orders1', function (Blueprint $table) {
$table->id();
$table->integer('user_id');
$table->integer('supplier_num');
$table->integer('supplier_id1')->nullable();
$table->integer('supplier_id2')->nullable();
$table->integer('supplier_id3')->nullable();
$table->decimal('supplier_subtotal1', 10, 2)->nullable();
$table->decimal('supplier_subtotal2', 10, 2)->nullable();
$table->decimal('supplier_subtotal3', 10, 2)->nullable();
$table->decimal('total', 10, 2);
$table->integer('product_num');
$table->integer('product_id1')->nullable();
$table->integer('product_id2')->nullable();
$table->integer('product_id3')->nullable();
$table->integer('product_id4')->nullable();
$table->integer('product_id5')->nullable();
$table->integer('qty1')->nullable();
$table->integer('qty2')->nullable();
$table->integer('qty3')->nullable();
$table->integer('qty4')->nullable();
$table->integer('qty5')->nullable();
$table->decimal('subtotal1', 10, 2)->nullable();
$table->decimal('subtotal2', 10, 2)->nullable();
$table->decimal('subtotal3', 10, 2)->nullable();
$table->decimal('subtotal4', 10, 2)->nullable();
$table->decimal('subtotal5', 10, 2)->nullable();
$table->text('detail')->nullable();
$table->timestamps();
});
}
適当にmigrateします。
$ php artisan migrate
Migrating: 2020_11_26_122330_create_orders1_table
Migrated: 2020_11_26_122330_create_orders1_table (53.41ms)
mysql> describe orders1;
+——————–+—————–+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+——————–+—————–+——+—–+———+—————-+
| id | bigint unsigned | NO | PRI | NULL | auto_increment |
| user_id | int | NO | | NULL | |
| supplier_num | int | NO | | NULL | |
| supplier_id1 | int | YES | | NULL | |
| supplier_id2 | int | YES | | NULL | |
| supplier_id3 | int | YES | | NULL | |
| supplier_subtotal1 | decimal(10,2) | YES | | NULL | |
| supplier_subtotal2 | decimal(10,2) | YES | | NULL | |
| supplier_subtotal3 | decimal(10,2) | YES | | NULL | |
| total | decimal(10,2) | NO | | NULL | |
| product_num | int | NO | | NULL | |
| product_id1 | int | YES | | NULL | |
| product_id2 | int | YES | | NULL | |
| product_id3 | int | YES | | NULL | |
| product_id4 | int | YES | | NULL | |
| product_id5 | int | YES | | NULL | |
| qty1 | int | YES | | NULL | |
| qty2 | int | YES | | NULL | |
| qty3 | int | YES | | NULL | |
| qty4 | int | YES | | NULL | |
| qty5 | int | YES | | NULL | |
| subtotal1 | decimal(10,2) | YES | | NULL | |
| subtotal2 | decimal(10,2) | YES | | NULL | |
| subtotal3 | decimal(10,2) | YES | | NULL | |
| subtotal4 | decimal(10,2) | YES | | NULL | |
| subtotal5 | decimal(10,2) | YES | | NULL | |
| detail | text | YES | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
+——————–+—————–+——+—–+———+—————-+
29 rows in set (0.00 sec)
続いて、このテーブルにSeederでテストデータを入れたい。
mysql> select version();
+———–+
| version() |
+———–+
| 8.0.22 |
+———–+
1 row in set (0.00 sec)
mysql> use test;
mysql> select * from users;
mysql> SELECT BENCHMARK(100,(SELECT id FROM users m0 WHERE (m0.id = 1)));
+————————————————————+
| BENCHMARK(100,(SELECT id FROM users m0 WHERE (m0.id = 1))) |
+————————————————————+
| 0 |
+————————————————————+
1 row in set (0.00 sec)
ん? 0秒ってこと?
$ touch mysql.sql
select * from users where id = 1;
$ mysql -u root -p "password" -D test <mysql.sql
$ cat mysql.sql | mysql -u root -p -vvv test | tail -n 3 Enter password:
1 row in set (0.00 sec)
Bye
$ time (cat mysql.sql | mysql -u root -p test > /dev/null) Enter password:
real 0m2.544s // プログラムの呼び出しから終了までにかかった実時間(秒)
user 0m0.013s // プログラム自体の処理時間(秒)(ユーザCPU時間)
sys 0m0.007s // プログラムを処理するために、OSが処理をした時間(秒)(システム時間)
0.013s秒ってことか。
これをレコード4000件で、テーブルのカラム数10, 30, 100でテストしてみるか。
結構大変だな。
# 前準備
1. テスト用の.envファイル作成
$ cp .env.example .env.testing
2. .env.testingのAPP_KEY作成
$ php artisan key:generate –env=testing
.env.testing
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=circle_test DB_USERNAME=hogehoge DB_PASSWORD=fugafuga
DB_DATABASEは、circle_testする。
3. テストコード作成
$ php artisan make:test UserRegisterTest
4. デフォルトのテストコードは削除
$ rm ./tests/Feature/ExampleTest.php
$ rm ./tests/Unit/ExampleTest.php
tests/Feature/UserRegisterTest.php
L RefreshDatabaseを使用すると、各テスト前後にマイグレーションとロールバックを実行
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class UserRegisterTest extends TestCase
{
use RefreshDatabase;
public function testExample()
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
5. UnitTestのテスト
$ vendor/bin/phpunit –testdox
PHPUnit 9.4.3 by Sebastian Bergmann and contributors.
User Register (Tests\Feature\UserRegister)
✔ Example
Time: 00:00.646, Memory: 26.00 MB
OK (1 test, 1 assertion)
6. ユーザ登録のテスト
$ php artisan route:list | grep register
| | GET|HEAD | register | register | Laravel\Fortify\Http\Controllers\RegisteredUserController@create | App\Http\Middleware\EncryptCookies |
| | POST | register | | Laravel\Fortify\Http\Controllers\RegisteredUserController@store | App\Http\Middleware\EncryptCookies |
tests/Feature/UserRegisterTest.php
class UserRegisterTest extends TestCase
{
use RefreshDatabase;
public function testUserRegister()
{
$email = 'hogehoge@gmail.com';
$this->post(route('register'), [
'name' => 'user1',
'email' => $email,
'password' => 'password',
'password_confirmation' => 'password'
])
->assertStatus(302);
$this->assertDatabaseHas('users', ['email' => $email]);
}
}
$ vendor/bin/phpunit –testdox
PHPUnit 9.4.3 by Sebastian Bergmann and contributors.
User Register (Tests\Feature\UserRegister)
✔ User register
Time: 00:00.463, Memory: 28.00 MB
OK (1 test, 2 assertions)
※functionはtestで始まる必要があり、userRegisterだと、No tests found in class “Tests\Feature\UserRegisterTest”となる。
これをCircleCIで行う
# CircleCIでのテスト
1. config/database.php
L circle_testingを追記する
'circle_test' => [
'driver' => 'mysql',
'host' => '127.0.0.1',
'port' => '3306',
'database' => 'circle_test',
'username' => 'root',
'password' => '',
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
2. GithubでPrivateのrepositoryを作成し、projectをgit pushします。
$ git init
$ git add .
$ git commit -m “first commit”
$ git remote add origin https://github.com/hoge/fuga.git
3. CircleCIにログインしてSetupProject

Add Configボタン押下
This package requires php ^7.3|^8.0 but your PHP version (7.1.33) does not satisfy that requirement.
config.ymlのdocker imageがphp:7.1になってるからエラー。ここを7.3以上に修正する必要がある。
– dockerはphp:7.4.7を、MySQLは8系を使う
https://circleci.com/docs/ja/2.0/circleci-images/
mysql8系から認証方法が変わったので、command: [–default-authentication-plugin=mysql_native_password]を追記
version: 2
jobs:
build:
docker:
- image: circleci/php:7.4.7-apache-node-browsers
- image: circleci/mysql:8.0
command: [--default-authentication-plugin=mysql_native_password]
environment:
- APP_DEBUG: true
- APP_ENV: testing
- APP_KEY: base64:hogehogefugafuga
- DB_CONNECTION: circle_test
- MYSQL_ALLOW_EMPTY_PASSWORD: true
working_directory: ~/repo
steps:
- checkout
- run: sudo docker-php-ext-install pdo_mysql
- restore_cache:
keys:
- v1-dependencies-{{ checksum "composer.json" }}
- v1-dependencies-
- run: composer install -n --prefer-dist
- save_cache:
paths:
- ./vendor
key: v1-dependencies-{{ checksum "composer.json" }}
- run: php artisan migrate
- run: php artisan db:seed
- run: php ./vendor/bin/phpunit
$ git add .
$ git config –global core.autoCRLF false
$ git commit -m “circleci”
$ git remote add origin https://github.com/hoge/hoge.git
$ git push -u origin master

復習にちょっと時間がかかり過ぎてしまったが、まぁOKでしょう。というか、Djangoで開発するときも、ちゃんとCircleCI使えばよかった。
これを実装していきます。
まずChart.jsの復習から
公式ドキュメントを元にテスト表示します。
<body>
<h1>折れ線グラフ</h1>
<div >
<canvas id="myLineChart" width="400" height="200"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script>
<script>
var ctx = document.getElementById('myLineChart').getContext('2d');
var chart = new Chart(ctx, {
type: 'line',
data: {
labels: ['january', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
label: 'ページビュー',
backgroundColor: 'rgb(0, 0, 0, 0)',
borderColor: 'rgb(81, 203, 206)',
data: [0, 10, 5, 2, 20, 30, 45],
}]
},
options: {
responsive: false
}
});
</script>
</body>
</html>

PHP側から日付とpageviewsの配列をjasonでjs側に渡す
$result = $data -> rows;
foreach($result as $key => $value){
$date[] = $value[0];
$pageviews[] = $value[1];
}
$date = json_encode($date);
$pageviews = json_encode($pageviews);
js側でJSON.parseで受け取る
data: {
labels: JSON.parse('<?php echo $date; ?>'),
datasets: [{
label: 'ページビュー',
lineTension: 0,
backgroundColor: 'rgb(0, 0, 0, 0)',
borderColor: 'rgb(81, 203, 206)',
data: JSON.parse('<?php echo $pageviews; ?>'),
}]
},
日付をYYYYMMDDではなく、本家のanalyticsみたいにMM月DD日にしたい。
-> substrでMMとDDを切り抜く
foreach($result as $key => $value){
$date[] = substr($value[0], 4, 2) . "月" . substr($value[0], 6) . "日";
$pageviews[] = $value[1];
}

これをユーザ入力で表示させます。
if(isset($_GET['datepicker_s']) && $_GET['datepicker_s'] !== ""){
$start_day = $_GET['datepicker_s'];
if(isset($_GET['datepicker_e']) && $_GET['datepicker_e'] !== ""){
$end_day = $_GET['datepicker_e'];
} else {
$end_day = date("Y-m-d");
}
} else {
if(isset($_GET['datepicker_e']) && $_GET['datepicker_e'] !== ""){
$start_day = date("Y-m-d",strtotime("-1 week", strtotime($_GET['datepicker_e'] . " 00:00:00")));
$end_day = $_GET['datepicker_e'];
} else {
$start_day = date("Y-m-d",strtotime("-1 week"));
$end_day = date("Y-m-d");
}
}

chartjsのtypeがlineだと、datasetが1つの時、以下の様にバグになってしまう。

type=”bar”とする


うーん、悪くないけど、やっぱ棒グラフが良いな
dimensionをga:dateにして、metricsをga:pageviewsにする。
$data[“rows”][0][“date”][“pv”] みたいな感じで返ってくるので、一度、rowsを取得してから、foreachで回すとdateとpvを取得できる。
$start_day = date("Y-m-d",strtotime("-1 week"));
$end_day = date("Y-m-d");
$data = $analytics->data_ga->get(
'ga:' . $profile,
$start_day,
$end_day,
'ga:pageviews',
array(
"dimensions" => 'ga:date',
"metrics" => 'ga:pageviews',
"sort" => 'ga:date',
)
);
$result = $data -> rows;
foreach($result as $key => $value){
echo '日付:' . $value[0]. ' PV:' . $value[1] . "<br>";
}

うん、これをchart.jsで表示したい。できれば、ユーザがformで日付(開始日、終了日)を入力して、それを表示。
PHPからJavascriptにjsonで渡して表示でOKだと思うんだが、Chart.jsの書き方忘れたから復習からだ。