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のコンンテナ用意したり、色々試して拡張して行けたらいいなと思います。
チームビルディングでドラッカー風エクササイズ!
部屋の隅っこでチームビルディング中…
背景
4月1日に入社された方を、私達のチームに迎え入れることになりました。
少しでも早くチームに慣れてもらえるように、チームビルディングをしてみたいなと思っていました。
たまたま「カイゼン・ジャーニー」を読んでいたところ、「ドラッカー風エクササイズ」というチームビルディングの手法が紹介されていたので、「これをやってみよう!」と思ったのがきっかけです。
カイゼン・ジャーニー たった1人からはじめて、「越境」するチームをつくるまで
- 作者: 市谷聡啓,新井剛
- 出版社/メーカー: 翔泳社
- 発売日: 2018/02/07
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (2件) を見る
ドラッカー風エクササイズって?
以下の4つの質問を基に、チームメンバーの期待をすり合わせ、チームビルディングをする手法です。
- 自分は何が得意か?
- 自分はどうやってチームに貢献するつもりか?
- 自分が大切に思う価値は何か?
- チームメンバーは自分にどういう成果を期待していると思うか?
詳細については、たくさん記事があるのでそちらをご参照ください。以下は主に参考にさせて頂いたサイトです。
- 「ドラッカー風エクササイズ」でチームメンバーからの期待を知る|ひのふ|note
- チームメンバーの期待をあわせる「ドラッカー風エクササイズ」 | DevTab - 成長しつづけるデベロッパーのための情報タブロイド
なんでこの手法をやろうと思ったのか
以下のようなことを考えていました。
- 新メンバーが、どんなスキルを持っているのか、どんな想いで新しい職場でやっていこうとしているのかを知りたい
- 既存メンバーが、どのようなスキルを持っているのか、どんな感じで仕事をしてきたのかを知ってもらいたい
つまり、相手の事を知りたいし、私達のことを知ってもらいたいという想いがあり、上にある4つの質問を基にしたコミュニケーションが、お互いの事を知るための良いツールだなと思ったので、実践することにしました。
やってみた
- 付箋に各質問に対する回答を書き出します
- 1人ずつ付箋をホワイトボードに貼り、発表をします
- 全員のターンが終わったら、4つ目の質問(自分にどういう成果を期待していると思うか?)に対し、実際に他のメンバーが期待している度合を以下のように定義し、付箋に書き出します
- 完全に合っていない
- だいたい合っていない
- 普通
- だいたい合っている
- 完全に合っている
このような手順で実施しました。手順化すると少し機械的に行っている様に見えますね・・・。
実際には、付箋を貼って発表した後、「例えば?」とか「具体的には?」といったように質問を入れたりして、会話をしながら進めるようにしました。
特に既存メンバーが発表する時は、具体例を出すことで新メンバーがイメージしやすいかなと思ったことと、 普段のチーム内の雰囲気を新メンバーに知ってもらいたいと思ったため、会話主体で進めるように意識しました。
まとめ
よかったこと
「得意なことは?」「どう貢献するか?」という質問に対し、ついスキル的なこと(どの言語ができる、とか)を回答しがちになってしまいましたが、 あるメンバーは「諦めない精神」や「みんなのモチベーションを上げる」といった性格的なことを回答していました。 特に新しくメンバーが入ってきた時にどんな人柄かを知る上で有効な回答だな、と思いました。
あるメンバーの感想で、「大切にしている価値観については知る機会がなかったのでよかった」というものがありました。 私達のチームは基本的にモブプロ(モブワーク)をしているので、普段からスキル的な事は把握し合えると思うのですが、価値観についてはそうそう話すことはなかったので、知る良い機会になりました。
反省点
- 約1時間立ちっぱなしで実施し、思っていた以上に疲れてしまいました。 タイムボックスを設けていなかったというのも反省点ですが、せめて椅子に座ってリラックスした状態で実施した方がよかったなと思います。
まとめ
新しいチームでやっていくぞという時や、新しくメンバーが加わる時など、お互いの事を知っていく上で効果を発揮するなと感じました。
また、以下のサイトにもあるように、チームの初期段階だけでなく、 ある程度時間が経過した後でも質問内容を変えたりすることで、その時々のチームの状態(メンバーが何を考えてるか等)を知り、もっと深く皆と向き合えるんじゃないかと思ったので、今後も活用していきたいです。
チームの期待値を合わせる!ドラッカー風エクササイズとタックマンモデルを組み合わせた結果 | Backlogブログ
今はまさに新しくメンバーを迎え入れる時期かと思うので、是非「ドラッカー風エクササイズ」を実施してみてはいかがでしょうか!? その際は椅子のあるお部屋で!
個人的ふりかえり
今月からブログを始め、心境や意識が大きくプラス方向に変化しました。
そのため、今月をふりかえり、出来事や思ったことなどを記録として残しておこう思いました。
チームの出来事
- モブプロで成功記録をとるようにしてみた
- 何が出来るようになったか(理解したか)、何が不明点なのか、を把握して成長を実感する糧になりました。
- ふりかえりフレームワーク「Fun Done Learn」を試してみた
- まだ1回しか試せていないものの、KPT よりもポジティブな意見が出やすくなる、ということを実感しました。
- 4月から毎日のふりかえりで実施していきたいです。
- チーム内勉強会(週3回、今のお題は PHP)
- 皆でより成長するために、30分でも、1時間でもやっていこう!と始めました。
- まずは継続!
個人の出来事
- ブログ・twitter 始めた
- 人生の転機と言ってもいいかもしれません。(機会をくださった方々に感謝です)
- アウトプットのためにブログを利用する、というイメージは持てていたのですが、それ以上にインプットを意識するようになりました。
- 本や、他の方が書かれた記事などを読むうえで、チームで取り組むとしたらどうか?自分で取り組むとしたらどうか?ということを意識するようになりました。
- AWS SAA に合格した
- 模擬問題65問を解くのは辛かった・・・。
- 自分でちゃんと触って、構築して、もっと理解を深めていこうと思うようになりました。
- 同僚が始めたコミュニティ「AliEaters Okinawa」に運営・企画のお手伝いで参加
- Alibaba Cloud を扱うコミュニティ。まだ使ったことないですが・・・。
alieaters-okinawa.connpass.com
まとめ
ブログを始めたことで、この1ヶ月で自分でもビックリするくらいの意識の変化がありました。
同僚や会社、世の中に価値あるものを届けられるような、そんなエンジニアになりたいと思うようになった1ヶ月でした。
明日からも頑張ろう。