php 7.2(Laravel)のcount()仕様変更でテーブル取得後の判定を修正
最近phpのバージョンを7.2に変更したら、count()関数の仕様が変わってしまって、いろいろ修正しなくてはいけなくなった。
今まで普通に動いていた箇所が、以下のようなエラーが出るようになった。
count(): Parameter must be an array or an object that implements Countable
マニュアルページを確認すると、以下のようにあった。
countable ではない型に対して count() (およびそのエイリアスである sizeof()) を使ったときに E_WARNING が発生するようになりました。
以下のようにLaravelでテーブルからのレコード取得後の件数があるかどうかで、分岐処理で戻り値を返すような処理をcount()関数を使って書いていることが多かったので、それらをすべて修正する羽目になった・・・。
$room = TrnRoom::where('room_name', $room_name) ->where('venue_id', $this->target_venue_id) ->where('delete_flag', false) ->first(); if(count($room) > 0){ return $room; }else{ return false; }
修正後はcount()の部分をempty()に変更が基本。
$room = TrnRoom::where('room_name', $room_name) ->where('venue_id', $this->target_venue_id) ->where('delete_flag', false) ->first(); if(!empty($room)){ return $room; }else{ return false; }
しかし、0件判定でemptyが有効なのはEloquentでテーブル取得処理を->first()で実行したときだが、->get()を使用した場合は0件でもempty()がfalse判定になるので注意。->get()で0件の場合は、コレクション型で中身なしのものが戻るので、その場合はempty()の判定がtrueになる。->first()の0件の場合はnullなので、emptyがtrue判定になる(->first(), ->get()の戻り値の型を意識しなくてもcount()で判定できていたので、count()をよく書くことになっていたのだった・・・)。
よって、->get()で取得する場合は、以下のような修正で対応した。
$room = TrnRoom::where('room_name', $room_name) ->where('venue_id', $this->target_venue_id) ->where('delete_flag', false) ->get(); if(room->isNotEmpty()){ return $room; }else{ return false; }
LaravelのコレクションにはisEmpty(), isNotEmpty()メソッドが使えるので、どちらかを使うとよい。
単純に件数が存在するかどうかを判定するにはisNotEmpty()でtrue判定を得た方がわかりやすい。
件数ありの場合でisEmpty()の場合をtrueにする場合は、!で反転する必要があるが、若干わかりにくい。
今年からブログを週1回は更新するといってまた放置気味になったので、よいネタができたと思っておこう。
2019年の技術的な目標
しばらくこのブログを放置してたけど、今年から再開したい。
去年までは忙しくてまったく更新はできていなかった。
日々プログラミングなどはやっていたので、書くネタはあったのだけど、更新まで手が回らず。
ということで、2019年ももう1週間が経過しようとしているが、今年の技術的な目標を設定しておきたい。
- Laravel(PHP)の細かい仕様まで詳しくなる
- JavaScript、特にjQueryの言語仕様を体系的に学習する
- 余力があれば他のJSフレームワークを学ぶ
- DB(MySQL≒MarriaDB)の知識強化(設計、DBの仕様、SQL文強化など)
- オブジェクト指向で設計をできるようになる
ざっくりと目標設定をするとこんな感じか。
本当はAWSの知識強化も入れたいのだけど、あまり手を広げすぎると結局手が回らなくなるので、自分の本業で使う部分、得意分野などを強化することにした。
2018年末で前職を退職し、年明け数か月は実質フリーランス的な感じにはなる。
やることはそんなに変わらず、開発中の以下のサービスを日々改善する感じ。
www.mice-concier.com
また、このブログは、なるべく週1回更新を目標としたい。
更新内容は主に自分が開発中にハマったところやTipsなどがメインかな。
あとは今年はなるべく勉強会とかカンファレンスなどにも参加していきたいなと思ったり。
ということで、よろしくお願いします~。
イベント、セミナー会場探しに役立つWebサービス『MICEコンシェル』を開発してローンチ
今年になってまったく技術ブログを更新していませんが、別にサボっていたわけでもなく、更新するまとまったネタがあまりなかったので、放置気味になってました。しかし、今回、今年1記事目は、自分がメイン開発者として携わったWebサービス、『MICEコンシェル』の紹介と宣伝です。
そもそもMICEとは何か?
そもそも「MICE」というのは、Meeting(会議・研修)、Incentive(招待旅行、travel, tour)、Conference(国際会議・学術会議)またはConvention、Exhibition(展示会)またはEventの4つの頭文字を合わせた言葉で、平たく言えば大小様々なイベントごとや研修、会議などの行事のことです。それをWebサービスの名称に含めてあります。
MICEコンシェルの概要
今回紹介する『MICEコンシェル』の概要を一言で説明すると、セミナー会場やイベント会場を検索し、会場ごとの比較表を作成、見積り/相見積り、予約/仮予約、イベント管理などをWeb上で一貫してできるようにするWebサービスです。
MICEとして会場を使うイベントをやろうとする場合は、まずは候補となる会場をリストアップし、それらの情報をネットで得つつ、比較表をExcelで作って検討し、各会場ごとに電話などで問合せをし、見積り、空き状況を確認し、様々なやりとりを会場とやって、やっと会場を決定してイベント当日を迎えます。会場が決まっても、机、椅子、マイク、プロジェクターなどのイベントに必要なものの準備も会場とのやりとりが継続して発生し、会場探しから、イベント準備までアナログでやろうとするとかなり時間と手間がかかってしまいます。それらの面倒な一連の作業を、すべてWeb上で完結できるようにしよう!!というのがMICEコンシェルです。会場探しから予約まで3分で完了させることを目標としています。
MICEコンシェルの主な機能
現在利用できる主な機能は以下となります。
- 会場の検索(料金、人数、利用シーンのカテゴリ、懇親会ができるなどのプランからなど)
- 会場ごとの比較表の作成
- 会場ごとに予約/仮予約/予約リクエスト/仮予約リクエストができる
- 会場ごとに見積り/相見積りができる
- 会場に質問や問合せ時に利用する掲示板機能
- 会場探し代行依頼
会場探し代行依頼というのは、検索して会場を比較したりするのが面倒な人向けで、大雑把に4月上旬に新宿近辺で50人くらいのセミナーの会場を探してほしい、というような依頼に対して、会場探しを代わりに依頼する投稿フォームのことです。直近で会場探しのに困っている方は以下からご相談できます。
https://www.mice-concier.com/organizer/inquiry
今後機能追加する機能は以下となります。
- イベント管理ツール
- コワーキングスペース対応
イベント管理ツールは、会場の利用先が確定した後に、イベントの準備に必要な机、椅子やマイクなど各種項目に対して、会場と詰めるべきものをWeb上で管理できるようにする機能です。こちらは自分が鋭意開発中で、3月中にはアップデート予定です。
現状のMICEコンシェルは、どちらかというとB to B領域の利用を想定しておりますが、コワーキングスペース対応は完全にto C向けの利用者のイメージです。個人ユーザーがいろいろなコワーキングスペースを検索して予約ができるようにするもので、こちらもなるべく早めに機能追加の対応をしていきたいと思います。
どういう人に使ってもらいたいか?
一番使ってもらいたいのは、様々な会社や組織で自分の本業とは別に片手間に社内イベントや就職セミナー、転職イベント、懇親会、大規模会議などの外部会場を探さなくてはいけない人です。その人たちの面倒な手間の解消をお手伝いできると思います。
会場の検索、予約の手間から解放されたい方はこちら
また、一般利用者とは別に、会場側のユーザーも会場登録は可能となります。2〜6月の就活イベントのピーク時以降の稼働率が下がる会場の担当者、もしくはホテルの宴会場、会議室など、稼働率を上げて売り上げも上げたい会場担当者の皆様にも送客のご協力ができると思います。
稼働率を上げたい会場担当者はこちら
どのような会場が登録されているか?
貸し会議室の大手のTKPさんやベルサールさんなど大規模会場から、小規模会議室、イベント会場も登録されております。まだ首都圏がメインですが、今後会場登録数を増やして全国で使えるようにしていく予定です。
以下からはエンジニア向けの雑感というか、余談となります。
MICEコンシェルの開発の経緯
転職してすぐに、自分の所属企業の社内ベンチャー的なプロジェクトに良くも悪くも巻き込まれ!?、2016年の3月くらいからずっとビジネスモデルなどを練りつつ、2017年年明けごろから本格開発し、2018/3/5(月)に一応β版を公開してローンチしました。開発者は全員で4人で、自分がだいたい全体の機能を3, 4割を開発しました。
主な使用技術
PHPは開発当初から7で行こうというとになり、フレームワークはLaravelが良いのではないか?ということで、Laravelへ。当初は5.3あたりを使っていて、徐々にアップデートして使ってます。自分はPHPのバージョンが4くらいのころに軽くWebアプリケーションを開発の経験がありましたが、最近のバージョンは全くで、ましてや他のPHPフレームワークも使ったことがない状態だったので、最初はLaravelの書き方などがわからなかったりでとまどりました。しかし、慣れてくるとDBの更新などmigrationファイルで定義できたりと、フレームワークは楽なんだなと実感できました。
このブログの従来の記事ネタは、Laravelで自分が特にハマったところを備忘録として書いてましたが、最近はハマることが少なくなってきた!?ので更新が滞りがちになりました。というのは半分本当ですが、もう半分はブログに書くほどの時間が取れなかったというのが大部分ですが・・・。
自分の担当機能、苦労した部分
技術ブログでもあるので、この際どこが苦労したか、技術的にアピールポイントであるかをちょっとだけ示しておきます。
フロント側はでは、主に会場、ルームごとの空き状況の在庫の表とそこの金額設定ですかね。ここは会場側の機能の在庫の管理と時間帯と曜日、パック料金などで料金を細かく設定できるものと連動させる必要がありました。なので、フロント側、会場側の予約機能と料金設定機能のほとんどを自分が設計して開発しました。料金を細かく設定して、フロント側でそれをうまく表示させるのが結構苦労しました。
また在庫表の予約の可否や予約の料金種類ごとにバリデーションをjQueryを駆使して実装するのも苦労しました。もともとJavaScript自体の経験もあまりない状態で、jQueryによるCSS操作、バリデーションなども調べながら実装したので、なかなか時間がかかりましたが、なんとか実装できました。特にjQueryを使ったバリデーションは地味なアピールポイントで、会員登録しないと見れないところであるので、ぜひ会員登録して確認してもらいたいと思います。
2017年の個人的な技術的振返り
今年から技術ブログを始めて、割とがっつりプログラミングもできたので、ちょっと振返っておこうと思う。
更新内容の方向性は以下だった。
結局あまり更新できておらず、10投稿くらいとなった。Tips的なエラーの解決方法を示していっていたけど、特にハマったやつで気づきにくいもので、かつそのときに更新の余裕があった場合のみで。
Laravelから始めるPHPフレームワーク開発
今年は主にLaravelをメインでガッツリプログラミングをできたと思う。PHPを本格的にやるのは大学時代以来。そのときはver.4.0くらいだったけど、今は7を使っている。Laravelも今年始めに5.3だったけど、今は5.5.24使っている。
Web系にそこまで経験があったわけでもないので、実質フレームワークを本格的に使うのは初めてだった。最初はよくわからなかったけど、慣れると楽だなと思った。特にDB周りのテーブル定義などmigrationファイルでサクッとできるのはよいなと。あとはbladeファイルでview側の定義をやって行くのも新鮮だった。昔Smartyとか使っていたけど。
ただ、PHPの宿命というか、MVCをキッチリ分けるのは難しいなと。定規杓子に分けすぎると結構処理が面倒になって、工数が結果的に増える場合もあって、そこはあまり厳密になりすぎても仕方ないなと思った。あとはPHPはコードが雑になりやすい気がする。型宣言を基本的にしなくてもよいし、個人的な癖でコレクションよりもなんでも配列で処理していたので、パフォーマンス的にどうとか全く考慮できてない。
DBはMaria DBを始めた
DB周りはMaria DBを使っていたけど、インデックスの貼り方やパフォーマンスを意識したSQL文を書けているかというと全然だなと。DB設計的なものは問題ないけど、インフラ寄りのパフォーマンスチューニングなどはあまり知識がないので、そこは補っていきたい。
JavaScriptの苦手意識克服
新技術習得という面では、JavaScript, jQueryあたりの基礎はできるようになった。といっても、ネット上のソースの改変レベルなのだけど。それでも特にjQueryの苦手意識があったので、セレクタの概念などが理解できて幅は広がったと思う。HTML, CSSの動的制御もある程度できるようになったので、フロント周りに技術力を特化できる方向性もありえる。
オライリーの洋書技術書を最後まで読めた
あと、もっと本当は技術本を読み込みたかったけど、結局プログラミング実践を優先してあまり読めなかった。しかし、Laravel本は何とか2冊ほど読めた。
Laravel リファレンス[Ver.5.1 LTS 対応] : 賢者の図書館 (Under Construction) : livedoor Blog(ブログ)
あと上記の読書ブログで更新してないけど、オライリーの洋書で。
Laravel本で翻訳さているもので、まともなのがなかったので、Kindleで辞書を使いながら通勤時間中にちょっとずつ読み込んだ。全部理解したわけでもないので、また再度読み込む必要があるけど。
理解度とは別に洋書技術書を最後まで読み込めたのは達成感がある。
laravelのmigrationで外部キー参照制約を一時無効化してchangeする方法
ちょっとしたTips。
外部キー参照制約を持つカラムを変更したいと思って、migrationファイルでchange処理を書いて実行すると、外部キー参照制約のエラーですとなった。
SQLSTATE[HY000]: General error: 1832 Cannot change column 'customer_scale_id': used in a foreign key constraint 'trn_reservations_customer_scale_id_foreign'
そういうときは、その変更対象のテーブルの外部キー参照制約を一時的に無効化して定義を変更すればよい。具体的には以下。
public function up() { //外部キー制約を一旦無効化 Schema::disableForeignKeyConstraints(); Schema::table('trn_reservations', function (Blueprint $table) { $table->string('inquiry', 2000)->nullable()->after('customer_scale_id'); //null不許可へ $table->unsignedSmallInteger('theme_id')->nullable(false)->change(); $table->string('organizer_company', 255)->nullable(false)->change(); $table->unsignedSmallInteger('customer_scale_id')->nullable(false)->change(); }); //外部キー制約を有効化 Schema::enableForeignKeyConstraints(); }
down処理時も同様に無効化して、戻す処理、有効化の順で記述してやる。一時的に無効化しているだけなので、migration実行後は外部キー参照制約は残ったままとなる。
ちなみにnot null制約への変更の nullable(false)->change(); はlaravel 5.5以上からできるようになったらしい。便利になった。
Laravelとinputのname属性の配列使用時のold値指定のエラーでハマった件
久しぶりによく分からないエラーにハマったので、備忘録的に示しておこう。
LaravelのView(bladeファイル)でinputを使用して、以下のような配列を受け渡すHTMLを作っていた。
<input type="text" name="price-0[]" value="100" class="align-right" style="width:100px;"> 円 <input type="text" name="price-0[]" value="200" class="align-right" style="width:100px;"> 円 <input type="text" name="price-1[]" value="300" class="align-right" style="width:100px;"> 円 <input type="text" name="price-1[]" value="400" class="align-right" style="width:100px;"> 円 <input type="text" name="price-2[]" value="500" class="align-right" style="width:100px;"> 円 <input type="text" name="price-2[]" value="600" class="align-right" style="width:100px;"> 円
name属性のprice-0は、POST受け渡し時にrequest値で配列として取得できる。そんで、これを列方向にも連番だけ違うname属性を増やして行列の2次元配列で渡すことを想定していた(上記HTMLはイメージなので不完全な構成)。
上記のHTMLを出力するためにinputを以下のように定義していた(変数$i, $jはそれぞれ行と列のループ処理用で、ループ処理記述は省略)。
<input type="text" name="price-{{$j}}[]" value="{{ old("price-".$j", $rateValues[$i][$j]) }}" class="align-right" style="width:100px;"> 円
そんで保存処理で以下のようなエラーになってしまった。
[2017-10-29 09:55:28] local.ERROR: htmlspecialchars() expects parameter 1 to be string, array given (View: /var/www/mice/laravel/resources/views/venue/reservation/configuration/room/form.blade.php) {"userId":1,"email":"test@test.jp","exception":"[object] (ErrorException(code: 0): htmlspecialchars() expects parameter 1 to be string, array given (View: /var/www/mice/laravel/resources/views/venue/reservation/configuration/room/form.blade.php) at /var/www/mice/laravel/vendor/laravel/framework/src/Illuminate/Support/helpers.php:577, ErrorException(code: 0): htmlspecialchars() expects parameter 1 to be string, array given at /var/www/mice/laravel/vendor/laravel/framework/src/Illuminate/Support/helpers.php:577)
htmlspecialchars()のエラーはあまり見ないので、最初はController側かと思ったけど、エラーメッセージはblade側を示している。htmlspecialchars()はblade内で変数出力するために使う{{ }}のところで内部的の呼び出されているらしいので、{{ }}を使っているところを見ても、エラーメッセージの示すstringが指定されるべきところに配列が渡っているところが分からず、結局怪しそうなところ(今回はold値を使用しているところ)を一つずつコメントアウトして、上記のinput部分であることは突き止められた。
結局以下のようにold値の最初の引数に1次元配列が渡っていたのが原因なので、そこをしっかり配列の中身の要素を渡すようにして解決した。
<input type="text" name="price-{{$j}}[]" value="{{ old("price-".$j.".".$i, $rateValues[$i][$j]) }}" class="align-right" style="width:100px;"> 円
name属性を2次元配列($jのようにname属性の連番で列方向を示し、各name自身は実質1次元配列)で渡す場合、old値の受け渡しもちゃんと price-".$j.".".$i で渡すのを見落としていて、4時間くらいハマってしまった(ビール飲んでからやってたので若干酔っていたというのもあるが)・・・。
ちなみに、name属性の配列の要素をold値で取得するには name名.要素番号 で指定できる。
Laravelのテーブルrename処理はmigrationファイルを分けた方が吉
このブログを放置しすぎているけど、久しぶりにハマったネタがあったので、更新しておこう。
Laravelでテーブルリネーム処理は、migrationファイル内で以下のように定義すれば良い。
Schema::rename('from_table_name','to_table_name');
このときテーブル名変更処理とカラム追加などの定義変更も同時にやろうと、同一migrationファイル内にリネーム処理と定義変更を以下のようにやってみた。
public function up() { Schema::rename('trn_reserv_rate_class_details','trn_reserv_time_rate_details'); Schema::table('trn_reserv_time_rate_details', function (Blueprint $table) { $table->renameColumn('reserv_rate_class_detail_id', 'reserv_rate_time_detail_id'); $table->unsignedInteger('reserv_time_class_id')->after('reserv_rate_class_id') ->references('reserv_time_class_id')->on('trn_reserv_time_classes') ->onupdate('restrict') ->ondelete('restrict'); $table->dropColumn(['start_time', 'end_time']); $table->dropForeign('trn_reserv_rate_class_details_reserv_rate_class_id_foreign'); $table->dropIndex('idx_reserv_rate_class_id_start_time_end_time'); }); }
このときにphp artisan migrateを実行してSchema::tableのテーブル内の定義変更の方でエラーになったりすると(インデックス削除処理の前に外部キー参照制約を消す定義をいれてなかったりでエラーになった)、テーブルリネーム処理だけが完了してしまう。migrationsテーブルを確認すると、このmigartionファイルの実行履歴はない。ということは、rollbackせずに再度php artisan migrateを実行できるはずだと思ってやってみると、すでにテーブルリネーム処理だけが完了しているので、テーブル名重複エラーになってしまう。
同様にphp artisan migrate:rollbackで実行するときのdown処理でもエラーになると、migrationsテーブルと実態のテーブルの状態がずれてしまって、rollback処理もうまくいかなくて、結局テーブル名を直接SQL文で変更するなり、対象テーブルを削除し、さらにmigrationsテーブルの履歴を削除したりして補正する手間が発生してしまった。
なので、テーブルリネーム処理とテーブル定義変更を同時にやる場合は、面倒だけどmigrationファイルを別に作ってそれぞれに定義して適切な順番で実行してやるとエラーになっても再度migaration, migaration:rollbackが問題なく実行できる。
この罠にハマって数時間浪費してしまったが、ネタができたと思っておこう。
あとはてなのソースコードのシンタックスハイライトにLaravel対応はないのかな?一応上記はPHPを指定しているのだけど。