チームビルディングでドラッカー風エクササイズ!(その2)
5月入社された方をチームに迎え入れることとなり、GW明け(5/7)にチームビルディングの一環としてドラッカー風エクササイズ
を行いました。
今回で2回目となります。この時に工夫したことや、よかったことなどをまとめます。
1回目は、4月に入社された方をチームに迎え入れた時に行いました。
ドラッカー風エクササイズ
についてや、その時の様子はぜひこちらをご参照ください。
目次
今回の様子
前回の立ちっぱなしで疲れる
という反省をふまえ、座って会話できる高さで表を書きました(笑)
メンバーは全部で5人いまして、1人はリモートワークをしており、台上のPCごしに見ている状況です。
右の男性(中国出身の方)は決して内職をしてるわけではなく、分からない単語をググっている様子、、、のハズです。
工夫したこと
質問項目
前回は初めてのトライということもあり、教科書通りの質問項目で行いました。
- 自分は何が得意か?
- 自分はどうやってチームに貢献するつもりか?
- 自分が大切に思う価値は何か?
- チームメンバーは自分にどういう成果を期待していると思うか?
しかし今回は、別の視点で他のメンバーの考えや想いなどを知りたいなと思い、質問項目を一部変えて行いました。
- スキルを何か1つマスターできるなら何がいい?
- 1ヶ月前から変わったと思うことは?
- 自分が大切に思う価値は何か?
- 最大のモチベーションは?
質問項目を自分たちで選んだということもあってか、回答がより具体的なものになり、会話も弾んだと思います。
進行
- ポストイットに回答を書き出す(10 分)
- 一人ひとりの発表(4 分 / 人)
1ヶ月前から変わったと思うことは?
について、他のメンバーから見て変わったと思うこと
を発表(キャプチャのピンクの付箋)- 気になったことなど、ざっくばらんに会話
3以降でタイムボックスを設けるのを忘れてしまいました・・・。 会話が発散しすぎないようにするためにも、次やるときは全体的にタイムボックスを設けようと思います。
まとめ
今回特によかったこは1ヶ月前から変わったと思うことは?
について、他のメンバーから見て変わったと思うこと
について会話できたことだと思います。
自身のこの1ヶ月のふりかえるだけでなく、他のメンバーの成長に注目していいところを指摘したり、ありがとう
を伝え合う場面もありました。
あるメンバーから「ふりかえりとか、モブプロ とか、チームでやっていくことについて色々取り組もうとしてくれてありがとう」という言葉をもらいました。
泣きそうになったのはナイショですが、むしろいつも私のフワッとした「こういうことやりたいんだよね〜」ということに対し、「よく分からないけど、まずはやってみよう!」と一緒に取り組んでくれるみんなにありがとう
を伝えたいです。(ちゃんと伝えたっけ・・・汗)
これからも自身のスキルアップだけでなく、主体的に動けるチーム、強いチームづくりをするための努力は続けていきたいなと改めて思いました。
Laravel の Eloquent 検証(ローカルスコープ)
DB からデータを取得する時、様々な条件のもとに取得してくる事が多いと思います。
その度にwhere
をいくつも連結して実装するのも、読み解くのも大変ですよね。
何が大変かと言うと、結局その実装で取得されるデータが何を表しているのか分かりにくい、というのが原因の1つなのではないかと感じました。
そういった事を解決してくれるクエリスコープ
という機能の1つであるローカルスコープ
について検証しました。
参考:Eloquent: Getting Started - Laravel - The PHP Framework For Web Artisans
ローカルスコープの検証
マイグレーションファイルの準備
以下のような users テーブルを用意します。
- is_active カラム:有効なユーザーかどうかを表すフラグ
role カラム(※ 例として以下のような分類とします)
admin
:管理者prime
:一般よりも権限を持つユーザーuser
:一般ユーザー
マイグレーションの定義
<?php public function up() { Schema::create('users', function (Blueprint $table) { $table->string('id', 36)->primary(); $table->string('last_name'); $table->string('first_name'); $table->boolean('is_active'); $table->enum('role', ['admin', 'prime', 'user']); $table->timestamps(); }); }
テストデータの準備
今回は、Faker
を使い、10人分のテストデータを生成しました。
Faker
はランダムなテストデータを生成するのに便利なライブラリです。
以下のサンプルのように人の名前だけでなく、email
や住所
なども生成してくれます。
参考:[PHP] Fakerでランダムなフェイクデータを作成する - Qiita
<?php $roles = ['prime', 'user']; $faker = Factory::create('ja_JP'); foreach (range(1, 10) as $i) { // 最初の1人だけ admin にし、それ以外はランダムで prime か user ロール。 $role = $i === 1 ? 'admin' : $roles[rand(0, 1)]; User::create([ 'id' => $faker->uuid, 'last_name' => $faker->lastName, 'first_name' => $faker->firstName, 'is_active' => $faker->boolean(), 'role' => $role, ]); }
生成したデータは以下の通りです。
検証1:シンプルな定義(有効なユーザーを取得する)
scope
をプレフィックスとした function をモデルに定義する必要があります。
ただし、この function を呼び出す時は、scope
を付けません。
モデル定義
ここでは、有効状態のユーザー
を操作する頻度が多い事を想定し、
以下のように User モデルにscopeActive
という名前で function を追加します。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; class User extends Model { 〜 略 〜 protected $casts = [ 'is_active' => 'boolean', ]; public function scopeActive(Builder $query): Builder { return $query->where('is_active', true); } }
実行
定義したスコープを使って件数を取得します(想定では8
人)。
<?php logger(User::Active()->count());
出力結果
[2019-05-06 08:59:09] testing.INFO: Query Time:15.99ms] select count(*) as aggregate from `users` where `is_active` = ? [2019-05-06 08:59:09] testing.INFO: array ( 0 => true, ) [2019-05-06 08:59:09] testing.DEBUG: 8
発行されるクエリがis_active
を条件にしている事、カウントも適切な値が取得できた事を確認できました。
少しハマったポイント
※ ドキュメントにはUser::active()
と、a
が小文字で書かれており、これを実行すると以下のようなエラーが発生しました。
試しにA
と大文字から書き始めたら正常に動くようになりました。
BadMethodCallException: Call to undefined method App\Models\User::acitve()
検証2:スコープに引数を渡せるようにする(動的スコープ)
モデル定義
role
がprime
もしくはuser
のリストを取得できるようにしたい、といったケースに対応できるようにするため、以下のようにscopeOfType
という function を追加します。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; class User extends Model { 〜 略 〜 // $type には `admin`, `prime`, `user` のいずれかを指定します。 public function scopeOfType(Builder $query, string $type): Builder { return $query->where('role', $type); } }
実行
定義したスコープを使い、prime
なユーザー数数を取得します(想定では3
人)。
<?php logger(User::OfType('prime')->count());
出力結果
[2019-05-06 09:21:46] testing.INFO: Query Time:20.1ms] select count(*) as aggregate from `users` where `role` = ? [2019-05-06 09:21:46] testing.INFO: array ( 0 => 'prime', ) [2019-05-06 09:21:46] testing.DEBUG: 3
このように、role
カラムを対象にprime
で絞っており、結果が3
である事も確認できました。
もちろん、User::OfType
に'user'
を渡せば、role
を対象にuser
で絞る事ができます。
<?php logger(User::OfType('user')->count());
[2019-05-06 09:39:39] testing.INFO: Query Time:17.09ms] select count(*) as aggregate from `users` where `role` = ? [2019-05-06 09:39:39] testing.INFO: array ( 0 => 'user', ) [2019-05-06 09:39:39] testing.DEBUG: 6
検証3:作成したスコープの合わせ技
実行
作成した2つの function を合わせて使用し、アクティブでrole
がuser
のユーザー数を取得します。(想定は4
人)
合わせる時は単純に繋げて使用する事ができます。
<?php logger(User::Active()->OfType('user')->count());
出力結果
[2019-05-06 09:43:51] testing.INFO: Query Time:17.5ms] select count(*) as aggregate from `users` where `is_active` = ? and `role` = ? [2019-05-06 09:43:51] testing.INFO: array ( 0 => true, 1 => 'user', ) [2019-05-06 09:43:51] testing.DEBUG: 4
is_active
およびrole
カラムを対象に絞り、結果が4
である事を確認できました。
感想
スコープの名称(function 名)を適切なもので定義する事で、その実装がどういうデータを取得してこようとしているか、判断しやすくなるなと感じました。 むしろ判断しやすくなるようなスコープを定義する必要があるかもしれません。
また、例えばあるカラムの名前を変更する事になった場合、そのカラムを利用するクエリ(例:where('カラム名', $value)
)を、スコープの中に閉じ込めておくことで、ソースコードの修正も容易になるのかなと思いました。
スコープを積極的に使用して、メンテナンス性の高いモデルを実装していきたいなと思いました。
Laravel の Eloquent 検証($dateFormat と $dates)
前回は Laravel の Eloquent を勉強するため、環境構築を行いました。
今回は日付に関する内容で、モデルのプロパティである$dateFormat
や$dates
について検証を行いました。
$dateFormat の検証
参考:Eloquent: Getting Started - Laravel - The PHP Framework For Web Artisans
created_at
やupdate_at
のフォーマットを変更した時の検証を、以下の2パターンで行います。
- デフォルトの timestamp にミリ秒も利用する(例:2019-05-06 10:10:22.123456)
- Unix 時間を利用する(例:1557105050)
デフォルトの timestamp にミリ秒も利用する
$dateFormat
プロパティにY-m-d H:i:s.u
を指定します。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model { public $incrementing = false; protected $keyType = 'string'; protected $guarded = [ 'created_at', 'updated_at', ]; protected $dateFormat = 'Y-m-d H:i:s.u'; // ← ここを追加 }
- マイグレーションファイルの編集
デフォルトの$tabe->timestamps();
では秒までしか登録されませんが、以下のようにtimestamps
に桁数を渡す事で、小数部分も登録されるようになります。
<?php public function up() { Schema::create('users', function (Blueprint $table) { $table->string('id', 36)->primary(); $table->string('last_name'); $table->string('first_name'); $table->timestamps(6); // ← 桁数を指定 }); }
- データ作成
ユーザーを1件登録します。
<?php User::create([ 'id' => Uuid::uuid4()->toString(), 'last_name' => '山田', 'first_name' => '太郎', ]);
このようにミリ秒も含めて登録される事が確認出来ました。
Unix 時間を利用する
- モデルの設定
$dateFormat
プロパティにU
を指定します。
<?php class User extends Model { 〜 略 〜 protected $dateFormat = 'U'; // ← ここだけ変更 }
- マイグレーションファイルの編集
Unix 時間が登録されるように、$tabe->timestamps();
を以下のようにinteger
を使って定義するようにします。
<?php public function up() { Schema::create('users', function (Blueprint $table) { $table->string('id', 36)->primary(); $table->string('last_name'); $table->string('first_name'); $table->integer('updated_at'); $table->integer('created_at'); }); }
- データ作成
ユーザーを1件登録します。
<?php User::create([ 'id' => Uuid::uuid4()->toString(), 'last_name' => '山田', 'first_name' => '太郎', ]);
このように Unix 時間で登録される事が確認出来ました。
感想
外部のサービスと連携する時など、サービスによっては日時のフォーマットが異なる場合もあるかと思います。 そして、外部サービスから受け取ったデータをそのまま DB へ保存したい時などに使えるのではないかと思いました。
$dates の検証
参考:Eloquent: Mutators - Laravel - The PHP Framework For Web Artisans
Eloquent は、データを取得するとデフォルトでcreated_at
とupdated_at
をCarbon
インスタンスへ変換してくれます。
created_at
とupdated_at
以外に日時を扱うカラムを追加した際に、$dates
でそのカラムを指定する事で、その指定したカラムもCarbon
インスタンへ変換してくれるようになります。
マイグレーションファイルおよびデータの準備
User のマイグレーションファイルに、以下のようなカラムを追加して検証を行います。
- カラム名:
logged_at
(Null を許可)- ユーザーがログインした日時を保存しておく想定
<?php public function up() { Schema::create('users', function (Blueprint $table) { $table->string('id', 36)->primary(); $table->string('last_name'); $table->string('first_name'); $table->timestamp('logged_at')->nullable(); // ← 追加 $table->timestamps(); }); }
次に、テーブルを作成後、以下のようなレコードを作成しておきます。
$dates を設定しなかった場合の挙動
まずは、User モデルに$dates
を設定せずにデータを取得してみます。
<?php $user = User::find('01a2b401-b03b-4471-9fe3-c015e22f0552'); logger($user->logged_at); logger(gettype($user->logged_at));
出力内容
[2019-05-06 03:33:23] testing.DEBUG: 2019-05-06 03:27:48 [2019-05-06 03:33:23] testing.DEBUG: string
このようにデフォルトではstring
で取得されます。
そのため、例えば2019年05月06日
と表示したい場合に、自前でCarbon
を使ったりdate
関数を駆使してフォーマットの変換を行わなければなりません。
$dates を設定した場合の挙動
それでは、以下のように User モデルに$dates
を設定します。
<?php class User extends Model { 〜 略 〜 protected $dates = [ 'logged_at' ]; }
再度、出力内容を少し変えて取得してみます。
<?php $user = User::find('01a2b401-b03b-4471-9fe3-c015e22f0552'); logger($user->logged_at); logger(get_class($user->logged_at)); logger($user->logged_at->format('Y年m月d日'));
出力内容
[2019-05-06 04:06:02] testing.DEBUG: 2019-05-06 03:27:48 [2019-05-06 04:06:02] testing.DEBUG: object [2019-05-06 04:06:02] testing.DEBUG: Illuminate\Support\Carbon [2019-05-06 04:06:02] testing.DEBUG: 2019年05月06日
このようにCarbon
インスタンスへ変換されたものが取得され、format
メソッドで簡単に書式を整える事ができました。
感想
正直言いますと、今まで取得したstring
の日付を頑張ってフォーマットしていました・・・。
もっと早く知っていれば!と後悔です。
おまけ
プライマリキーは UUID で
検証で使う User モデルの id を、デフォルトのもの(int)ではなく、以前勉強したUUID
で利用できるようにしています。
モデルの置き場所を変更
モデルはapp/Models
下に置くように変更しました。
参考:【Laravel】Model ファイルのディレクトリ構成変更時にやること | ブロックチェーンエンジニアのブログ
実行された SQL を確認したい
下記参考サイトを真似させて頂きました。
※ 出力される SQL の実行時間($query->time
)の単位はミリ秒
です。
<?php namespace App\Providers; use Illuminate\Support\Facades\{DB, Log}; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { /** * サービスプロバイダの登録 * * @return void */ public function register() { // } /** * アプリケーションサービスの初期処理 * 参考サイト:https://daiki-sekiguchi.com/2018/07/26/laravel-sql-log/ * * @return void */ public function boot() { if (config('app.env') !== 'production') { DB::listen(function ($query) { Log::info("Query Time:{$query->time}ms] $query->sql"); Log::info($query->bindings); }); } } }
Docker で Laravel の環境構築をしました
やりたい事
- Laravel の Eloquent の機能を色々試したい
- 単体テスト形式でコードを書いて試したい
- テスト実行時には、開発用 DB とは別にテスト用の DB を用意したい
Eloquent を勉強するため、まずは検証環境を用意する事から着手しました。 その時の過程やハマったポイント等をまとめました。
環境情報
- Docker version 18.09.2
- docker-compose version 1.23.2
アプリ(Laravel)の環境構築
こちらの記事を参考にさせて頂きました。構成はほぼ同じです。
ただ、PHP のバージョンは 7.3 で作成したかったため、一部変更して実行しました。
まずはアプリのみを構築(Laravel をインストールするまで)
- docker-compose.yml
version: '3' services: php: container_name: php build: ./docker/php volumes: - ./server:/var/www nginx: image: nginx container_name: nginx ports: - 80:80 volumes: - ./server:/var/www - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf depends_on: - php # db に関するものはいったん削除しています。(後で追加)
docker/php/Dockerfile を変更
PHP 7.3 を使いため、イメージ指定を変更しました。
また、apt-get でインストールするものにlibzip-dev
を追加しました。(ハマったポイントその1)
- docker/php/Dockerfile
FROM php:7.3-fpm # ← 7.2-fpm から変更 COPY php.ini /usr/local/etc/php/ RUN apt-get update \ # ↓ libzip-dev を追加 && apt-get install -y zlib1g-dev libzip-dev mysql-client \ && docker-php-ext-install zip pdo_mysql # Composer install RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" RUN php -r "if (hash_file('sha384', 'composer-setup.php') === '48e3236262b34d30969dca3c37281b3b4bbe3221bda826ac6a9a62d6444cdb0dcd0615698a5cbe587c3f0fe57a54d8f5') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" RUN php composer-setup.php RUN php -r "unlink('composer-setup.php');" RUN mv composer.phar /usr/local/bin/composer ENV COMPOSER_ALLOW_SUPERUSER 1 ENV COMPOSER_HOME /composer ENV PATH $PATH:/composer/vendor/bin WORKDIR /var/www RUN composer global require "laravel/installer"
後は、参考サイト通りに Laravel をインストールし、動作確認(Laravel と書かれたページが表示)まで実施します。
※ 次に行く前にいったん環境を停止しておきます。
$ docker-compose down
DB の環境構築
開発用 DB とは別にテスト用 DB を作成するため、また、テスト実行時にはちゃんとテスト用 DB へ向ける必要があるため、以下のサイトを参考にして進めました。
以下のルールで構築します。
- 開発用 DB 名は
study
- テスト用 DB 名
study_testing
DB コンテナの定義を追記
MySQL は root ユーザーで運用したいため、MYSQL_USER
,MYSQL_PASSWORD
を削除しました。(※ 他ユーザーへの権限付与等の手間を省きました。手を抜きました。。。)
- docker-compose.yml
version: '3' services: php: 〜 略 ~ nginx: 〜 略 〜 db: image: mysql:5.7 container_name: db-host environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: study # DB 名を変更 # MYSQL_USER, MYSQL_PASSWORD は削除 TZ: 'Asia/Tokyo' # ↓ utf8mb4_general_ci に変更 command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci volumes: - ./docker/db/data:/var/lib/mysql - ./docker/db/my.conf:/etc/mysql/conf.d/my.cnf - ./docker/db/sql:/docker-entrypoint-initdb.d ports: - 3306:3306
server/config/database.php を変更
テスト用 DB への接続情報を追記します。
- server/config/database.php
<?php 'connections' => [ 'sqlite' => [ 'driver' => 'sqlite', 'database' => env('DB_DATABASE', database_path('database.sqlite')), 'prefix' => '', 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), ], 'mysql' => [ 'driver' => 'mysql', 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_general_ci', // ← utf8mb4_general_ci に変更 'prefix' => '', 'prefix_indexes' => true, 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], // ↑ の 'mysql' をキーとするものをコピーして、キーを変更します。 'mysql_testing' => [ 'driver' => 'mysql', 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_general_ci', 'prefix' => '', 'prefix_indexes' => true, 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), ]) : [], ],
server/.env を変更
DB_HOST が 127.0.0.1
だったので、DB のコンテナ名を指定します。(※ ハマったポイントその2)
- server/.env
〜略〜 DB_CONNECTION=mysql DB_HOST=db-host # DB コンテナの名前を指定 DB_PORT=3306 DB_DATABASE=study DB_USERNAME=root DB_PASSWORD=root 〜略〜
server/.env.testing ファイルを作成
server/.env ファイルをコピーして作成します。テスト時はstudy_testing
を見にいくようにします。
- server/.env.testing
〜略〜 DB_CONNECTION=mysql_testing # ← テスト用の方を見るように指定します。 DB_HOST=db-host DB_PORT=3306 DB_DATABASE=study_testing # ← テスト用 DB を見るように指定します。 DB_USERNAME=root DB_PASSWORD=root 〜略〜
テスト用 DB 作成 SQL を用意(※ハマったポイントその3)
コンテナ起動時にテスト用 DB が作成されるように SQL ファイルを以下の場所に作成します。
CREATE DATABASE IF NOT EXISTS study_testing CHARACTER SET utf8mb4;
コンテナを起動する
$ docker-compose up -d
これで、ローカルから MySQL Workbench 等を使って DB コンテナへアクセスすれば、study
と study_testing
という DB が作成されている事が確認出来るかと思います。
マイグレーション
まだ各 DB の中身は空っぽなので、Laravel のマイグレーションを利用してテーブルを作ります。
アプリ(Laravel)のコンテナに入る
以下のコマンドでコンテナに入ります。
$ docker-compose exec php bash
開発用 DB に対してマイグレーション
$ php artisan migrate
これで DB study
内に users テーブル等が作成されます。
テスト用 DB に対してマイグレーション
--env=testing
を指定する事で、server/.env.testing
ファイルを読み込んで実行する事が出来ます。
$ php artisan migrate --env=testing
これで、開発用 DB と同様に、study_testing
にもテーブルが作成されます。
以上で、おおかた環境を整える事が出来ました。
Eloquent 10 本ノックの本編は次回に書きたいと思います!
ハマった事
ハマったポイントその1
$ docker-compose up -d
実行時にエラー
最初、libzip-dev
は追記せずにイメージだけを変更して、
$ docker-compose up -d
を実行したところ、以下のようなエラーが出ました。
〜略〜 checking for pkg-config... /usr/bin/pkg-config checking for libzip... not found configure: error: Please reinstall the libzip distribution ERROR: Service 'php' failed to build: The command '/bin/sh -c apt-get update && apt-get install -y zlib1g-dev mysql-client && docker-php-ext-install zip pdo_mysql' returned a non-zero code: 1
PHP 7.3 では、このlibzip-dev
が必要なようですが、そのソースまでは辿り着けませんでした。。
ハマったポイントその2
$ php artisan migrate
でSQLSTATE[HY000] [2002] Connection refused
エラー内容の詳細
$ php artisan migrate Illuminate\Database\QueryException : SQLSTATE[HY000] [2002] Connection refused (SQL: select * from information_schema.tables where table_schema = database and table_name = migrations) at /var/www/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664 660| // If an exception occurs when attempting to run a query, we'll format the error 661| // message to include the bindings with SQL, which will make this exception a 662| // lot more helpful to the developer instead of just the database's errors. 663| catch (Exception $e) { > 664| throw new QueryException( 665| $query, $this->prepareBindings($bindings), $e 666| ); 667| } 668| Exception trace: 1 PDOException::("SQLSTATE[HY000] [2002] Connection refused") /var/www/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70 2 PDO::__construct("mysql:host=127.0.0.1;port=3306;dbname=database", "root", "root", []) /var/www/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70 Please use the argument -v to see more details.
DB にアクセス出来ないよ、というエラーが発生しました。
この時、ローカルの MySQL Workbench からは接続が出来て、空っぽのstudy
データベースが見れる事を確認しました。
そのため、php コンテナから db-host コンテナへのアクセスする設定が何かしら足りないんじゃないかと疑いました。
そして、server/.env
ファイルを見ると、
DB_HOST=127.0.0.1
となっていたので、これを DB のコンテナ名 db-host
にする事で解決出来ました。
ハマったポイントその3
./docker/db/sql
に置いた SQL ファイルが実行されない問題
テスト用の DB を用意する前に Laravel の環境を構築していたわけですが、その際にはもちろん DB が構築されており、
docker/db/data
にデータが作成されている状態でした。
以下の参考サイトにもあるように、データが永続化されている場合には、初めての起動時にしか ./docker/db/sql
に置いた SQL は実行されないようです。
- 参考サイト
開発段階では、データを永続化したいですし、
今更ではありますが、開発用 DB とテスト用 DB を分けて考える場合、同一コンテナではなくて別々のコンテナを用意してあげればよかったのかもしれません。。
まとめ
- やはり環境構築は骨が折れます。。(慣れるしかないかな!?)
- Docker 使ってるおかげでいくらでも作って、消してが試せる!(ローカルが汚れる心配なし!)
- 次回は、いよいよ Eloquent を触って行きます!
AliEatersOkinawa Meetup #1 で LT しました!
2019年4月24日(水)に、沖縄で初となる Alibaba Cloud Developers Community のミートアップを開催しました。
そこで人生2回目の LT をしましたので、その時の発表資料や、言い忘れた事(汗)、補足したい事などを書きたいと思います。
alieaters-okinawa.connpass.com
Alibaba Cloud についてはコチラ
いざ、LT!(発表資料)
発表時の風景
発表内容の概要
Alibaba Cloud の以下のサービスを使って 簡単な API を作ってみました。
作った API はユーザーを1件作成するものと、ユーザーリストを取得する、という簡単なものです。
発表途中で、ソースコードをデプロイし、デプロイされた API を Postman を使って実行する、といったことを行いました。
※ Postman は API を開発する時にとても便利なツールです。API を実行するだけでなく、テストも書けてしまうという優れものです。
こんな感じで API を実行しました。
まずは、登録したいユーザーの情報を Body に JSON で定義して POST。
次に、リストを取得する GET して、先ほど作成したユーザーが取得されました。
伝えたかった事
正直サービスを触ったばかりですし、このサービスのこの機能が素晴らしいんだよ!といった事は言えません。。
しかし、いくつかつまづいたポイントがあるにしても、Serverless な API をサクッと作成+デプロイまで持っていけるのは便利だなと感じました。
API や NoSQL(Table Store)の勉強にも役立てる事ができますし、業務の中でも、プロトタイプをサクッと作りたい時なんかに有効活用できるんじゃないかと思いました。
まとめ
意識した事
- 発声
まだ LT 2回目だし発表内容の質はこれから上げていこう、という甘えを抱きつつも、 せめて何を言っているか聞き取りやすいように、腹式呼吸を心がけました。(カラオケの時みたいに)
後日、LTを聞いてくれていた同僚に言葉が聞き取りやすかったか聞いてみたところ、「聞き取りやすかったよ〜」と言ってくれたので、ひと安心しました。 次からも意識していこうかな、と。
反省点(次に繋げる事)
- 時間オーバー
10分の枠でしたが、デモの操作で時間がとられてしまい、枠を越えてしまいました。 デモ部分を動画にするなどの工夫が必要かなと思いました。
参考にしたサイト
Laravel の主キーで UUID を利用する時にハマった事、調べた事
起きた問題
テーブルの id カラムを UUID で登録できるようにしていました。
例えば、以下のような users テーブルがあるとします。
テーブル名:users
id | name |
---|---|
62ce6b67-f8e3-4279-a2a1-0a23f3ca5730 | はてな 太郎 |
830fd685-58dc-46b5-b7df-16e06f3c1b34 | はてな 花子 |
単純にユーザーの一覧を取得したく、User::all()
を実行してみたところ、id が上記のような 36 桁の文字列ではなく、以下のように id が欠落された状態で取得されてしまいました。
{ "data": [ { "id": "62", "name": "はてな 太郎" }, { "id": "830", "name": "はてな 花子" } ] }
これを解決すべく調べた事や、試した事などをまとめます。
原因
まさにコレだ!というものが Laravel のドキュメントに書かれていました。
Eloquentは主キーを自動増分される整数値であるとも想定しています。つまり、デフォルト状態で主キーは自動的にintへキャストされます。
※ Eloquent:利用の開始 5.8 Laravel の「主キー」
この仕様のため、各 id の最初の数字部分(830
と 62
)だけが取得された事になります。
私はコレで解決しました
上記の Laravel のドキュメントと同じ箇所にこのように書かれていました。
自動増分ではない、もしくは整数値ではない主キーを使う場合、モデルにpublicの$incrementingプロパティを用意し、falseをセットしてください。主キーが整数でない場合は、モデルのprotectedの$keyTypeプロパティへstring値を設定してください。
これを読んだ時に正直思った事は、 $incrementing
と $keyType
の両方を設定しないといけないの?それとも片方だけでいいの?でした。
まずは、他の方のサイトも参考にしつつ、両方のプロパティを設定する事にしました。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model { public $incrementing = false; // ← 追加 protected $keyType = 'string'; // ← 追加 protected $fillable = ['id', 'name']; }
これらのプロパティを追加後にデータ取得し直してみると・・・
{ "data": [ { "id": "62ce6b67-f8e3-4279-a2a1-0a23f3ca5730", "name": "はてな 太郎" }, { "id": "830fd685-58dc-46b5-b7df-16e06f3c1b34", "name": "はてな 花子" } ] }
といった具合に、今度は id の一部が欠落されることなく取得することが確認できました。
以上です。。。
だけだとつまらないので、最初に思った疑問「プロパティ両方必要?それとも片方だけ?」を少し試しました。
プロパティ両方必要?それとも片方だけ?
結果を先に言いますと、どちらか片方だけでも想定通りの動き(id がちゃんと文字列 36 桁で取得)をしました。
// public $incrementing = false; protected $keyType = 'string';
でも
public $incrementing = false; // protected $keyType = 'string';
データの登録、取得で問題なく動きました。
しかし、なんだか腑に落ちないのでもう少し調べてみました。
Model に定義されているデフォルト値
Laravel の Illuminate\Database\Eloquent\Model
の中身を見てみたところ、以下のようにデフォルト値が設定されていました。
なので、どちらかのプロパティを設定して想定通りに動いたとしても、そのモデルのプロパティ値が気持ち悪い状態だな、と思いました。
$incrementing は false で、$keyType は int であれば id に自前で用意した数値を入れるんだな、という感じですが、 $incrementing は true だけど $keyType は string の時に、矛盾しているように感じました。
/** * The "type" of the auto-incrementing ID. * * @var string */ protected $keyType = 'int'; /** * Indicates if the IDs are auto-incrementing. * * @var bool */ public $incrementing = true;
そこで、これらのプロパティが使われている(何かしらの条件で使われている)箇所を調べてみたところ、以下のような処理が実行されていることが分かりました。
Model クラスの 782 行目(私の環境では)
// If the model has an incrementing key, we can use the "insertGetId" method on // the query builder, which will give us back the final inserted ID for this // table from the database. Not all tables have to be incrementing though. $attributes = $this->getAttributes(); if ($this->getIncrementing()) { $this->insertAndSetId($query, $attributes); } // If the table isn't incrementing we'll simply insert these attributes as they // are. These attribute arrays must contain an "id" column previously placed // there by the developer as the manually determined key for these models. else { if (empty($attributes)) { return true; } $query->insert($attributes); }
$this->getIncrementing()
は、モデルの $incrementing の値を返しています。
そのため、例え $keyType が string に設定していて動いていても、この insertAndSetId が実行されてしまいそうです。 これ以上先は追いきれませんでした・・・。
insertAndSetId が実行されても問題なく動くのですが、ムダな処理が実行されてしまいそうです。
なのでやはり id に UUID を登録したい時には、両方のプロパティを設定した方が良さそうに思いました。
補足
Laravel のマイグレーションファイルで、id を UUID が登録できるようにするために、以下のように定義してテーブルを作成しました。
Schema::create('users', function (Blueprint $table) { $table->string('id', 36)->primary(); $table->string('name'); $table->timestamps(); });
まとめ
今回はちょっとした事をきっかけに、フレームワークの中身を少しだけですが追ってみました。
ソースコードを目で見て追うだけでなく、実際にどう動いているのかを確認できたら面白いんだろうな、と感じました。
この辺は次なる課題として、、、もっと手を動かして実験して、まとめていけるようになりたいなと思います。
PHP で遊ぶための環境を構築してみた
はじめに
4月に入り、PHP のスキルを上げ、開発スピードを上げよう!というチーム目標を立てました。
勉強は各自に任せて、だと何だか気持ち的な負担が高くなるような気がしたので、皆で PHP の勉強会をするようになりました。
そこで必要になったのが実行環境です。
本を読むだけでなく、実際に手を動かして試せる環境が欲しいなと思ったので、Docker の勉強がてら Docker + Composer + PHPUnit を使った環境構築に挑戦してみました。
それぞれを簡単にざっくり言うと・・・
準備するもの
ローカル環境に以下のものがインストールされている事
Docker
- ※ 私の環境のバージョンは 18.09.2 でした
ディレクトリ構成
- test-php - app - src Calculator.php - tests SampleTest.php - docker - php7 Dockerfile - html index.php composer.json docker-compose.yml
以下は各ファイルの内容です。
- docker-compose.yml
version: '3' services: test-php7: build: docker/php7 container_name: 'test-php7' ports: - '8000:80' working_dir: /workspace volumes: - ./:/workspace - ./html:/var/www/html
- docker/php7/Dockerfile
FROM php:7-apache RUN apt-get update && apt-get install -y \ # phpunit をインストールする時に必要 zip # root でのインストールを許可する設定 ENV COMPOSER_ALLOW_SUPERUSER 1; # composer をインストールする RUN curl -s http://getcomposer.org/installer | php \ && mv composer.phar /usr/local/bin/composer
- composer.json
{ "require-dev": { "phpunit/phpunit": "^8.1" }, "scripts": { "test": [ "phpunit" ] }, "autoload": { "psr-4": { "App\\": "app/" } } }
以下の PHP ファイルは単純なサンプルコードなので省略しています。
Calculator.php
<?php namespace App\Src; class Calculator { /** * 足し算をする * * @param int $a * @param int $b * @return int */ public static function add(int $a, int $b): int { return $a + $b; } }
SampleTest.php
<?php namespace App\Tests; use App\Src\Calculator; use PHPUnit\Framework\TestCase; class SampleTest extends TestCase { public function testSample() { // 期待する値 $expected = 3; // テストしたい function を実行 $result = Calculator::add(1, 2); $this->assertEquals($expected, $result); } }
仮想環境の構築
コンテナを起動する
- まずは、
test-php
ディレクトリでコンンテナを起動します。-d
はバックグラウンドで実行するためのオプションです。- これを実行する事で、PHP が実行でき、さらに Composer がインストールされた状態の仮想環境が構築されます。
$ docker-compose up -d
PHPUnit をインストールする
- Composer を使って PHPUnit をコンテナ内にインストールします。
$ docker exec test-php7 composer update
※メモ
docker exec を使い、コンテナ内のコマンド(composer update
)を実行しました。
$ docker exec [コンテナ名] [コマンド]
テストを実行
- ローカルからテストを実行する
$ docker exec test-php7 composer test -- app/tests
※ コンテナ内のコマンドにオプションを渡す時は、--
で一旦区切りを入れてから指定します。
この場合 $ docker exec test-php7 composer test app/tests
はエラーになります。
テスト実行結果
> phpunit 'app/tests' PHPUnit 8.1.2 by Sebastian Bergmann and contributors. . 1 / 1 (100%) Time: 247 ms, Memory: 4.00 MB OK (1 test, 1 assertion)
これで、ローカルに PHP の実行環境を構築する事ができました。
今回の構築手順としては、
という大きく分けて2手間が必要な手順になりました。
Docker もまだまだ不明な点がたくさんあるので勉強しつつ、 今後の展望としては、DB(MySQL)のコンテナを用意して DB 接続を含めたテストを書いたりできるようにしてみたいなと思います。
ハマったポイント
1. PHPUnit をインストールする時に発生した警告!
コンテナの中に入って、手動で PHPUnit をインストールしてみた時に以下のようなメッセージが表示されました。
$ composer require phpunit/phpunit --dev Do not run Composer as root/super user! See https://getcomposer.org/root for details
このメッセージが出ても中断される事なく PHPUnit はインストールされました。 どうやら root ユーザーでインストールを行う時に、セキュリティ的によろしくないよ、という事で警告が出ているようです。
ではどう対応するのがベストなのか?
・・・
ローカルで動かすだけですし、、、すぐには分からなかったため、Dockerfile に以下の設定を付けました。 (メッセージが出なくなるだけで、根本的な解決ではないと思いますが)
# root でのインストールを許可する設定 ENV COMPOSER_ALLOW_SUPERUSER 1;
2. テスト実行時に Class Not Found!
PHP のクラスを作っている時には、IDEの補完が効いてくれていたので、つい一方のクラスからもう一方のクラスの参照が出来ているものと思ってしまいました。
実は以前にも同じような事にハマった事がありまして、エラーが出た時に「あぁあれね」と気付けたのですぐ解決できました。(一瞬ヒヤっとしましたが・・・)
※ 以前ハマった時の記事はこちら
解決するために、composer.json に以下のように autoload
の設定を追加しました。
"autoload": { "psr-4": { "App\\": "app/" } }
まとめ
- 環境構築は不慣れなため、ちょっとしたコマンドを用意するだけでも時間がかかってしまいました。
- 時間がかかってしまった分、達成感を味わえたと思います。
- ひとまず動くところまでできたけれども、この構成で良いのか?という不安があります。
- まずは第一歩という事で、これからDBのコンンテナ用意したり、色々試して拡張して行けたらいいなと思います。