Debug My Life.

いろいろデバッグをがんばるブログ。

2017年の個人的な技術的振返り

今年から技術ブログを始めて、割とがっつりプログラミングもできたので、ちょっと振返っておこうと思う。

更新内容の方向性は以下だった。

このブログの更新内容 - Debug My Life.

結局あまり更新できておらず、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で辞書を使いながら通勤時間中にちょっとずつ読み込んだ。全部理解したわけでもないので、また再度読み込む必要があるけど。

shop.oreilly.com

理解度とは別に洋書技術書を最後まで読み込めたのは達成感がある。

まとめ

最後に、なんだかんだでプログラミングをガッツりできたし、プログラミングをやっている時が集中できるし、達成感もあるし、成長を実感できた。ドラクエでレベルが上がるたびに一つずつ呪文、特技を覚えていくような心踊る感覚だった*1。その感覚がまだあるので、自分のプログラミング能力の向上の余地はまだまだあるのではないかと思った。

今年はWeb系の技術の基礎は大体習得できたと思う。それが自分にとって、2017年の1年で達成した大きなことのように思える。

*1:ちなみにドラクエ11もなんだかんだでプレイできて、かなり楽しめた。

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実行後は外部キー参照制約は残ったままとなる。

データベース:マイグレーション 5.5 Laravel

ちなみにnot null制約への変更の nullable(false)->change(); はlaravel 5.5以上からできるようになったらしい。便利になった。

qiita.com

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を指定しているのだけど。

jQueryでaタグの特定リンクのみ有効化する方法

放置してた技術ブログを少しずつ再開しないとなと思っていたところ、jQueryではまったところが出てきたのでメモ更新。

プレビュー画面のようなものを作っている時、プレビューの機能的には見た目のみの確認で、リンク先に遷移させると機能的によろしくないときはHTMLのリンクaタグを無効化したい。それはjQueryで以下のように簡単に制御できる。

  $(function(){
      $('a').click(function(){
          return false;
      })
  });

これはaタグすべてが無効化になるが、一部有効化しておきたい場合がある。その場合は、aタグ内にclassを設定し、そのクラスの有無で条件分岐すればよいらしい。

<a href="../sample.html" class="enable">

有効化したいaタグに class="enable" を追加。
特定クラスの有無の分岐はhasClassメソッドを使う。

  $(function(){
      $("a").click(function(e){
          if($(e.target).hasClass("enable")) {
              return true;
          } else {
              return false;
          }
      });
  });

これでclass="enable"を持つリンクのみ有効化され、それ以外の普通のaタグはすべてリンクが無効化になる。

また、特定リンクのみ無効化する方法は、例えばaタグ内にclass="disable"を設定し、hasClassを使わずに以下の書き方でできる。

  $(function(){
      $('a.disable').click(function(){
          return false;
      })
  });

特定リンクのみ無効化する方法についてはググるとたくさんでてくるけど、全体のリンクを無効化しつつ、一部有効化する方法がググってもあまり見当たらなかったので、調べつつ試行錯誤してなんとかできた。

他にもLaravelネタで更新することはあるけど、結局面倒で後回しになってる・・・。
本当は週1くらいで更新していきたいところなのだけど。土日どちらか1回は更新目標にしよう。

php artisan migrateのエラー確認方法

LaravelのMigrationファイルをDBに反映するコマンド:php artisan migrateを実行してエラーになったときに、以下のようにコンソール上はエラー箇所がよく分からないことが多い。

php artisan migrate
Migration table created successfully.

  [Illuminate\Database\QueryException]
  SQLSTATE[42S01]: Base table or view already exists: 1050 Table 'users' already exists (SQL: create table `users` (`id` int unsigned not null auto_increment primary key, `name  
  ` varchar(255) not null, `email` varchar(100) not null, `password` varchar(255) not null, `remember_token` varchar(100) null, `created_at` timestamp null, `updated_at` timest  
  amp null) default character set utf8mb4 collate utf8mb4_unicode_ci)           

  [PDOException]
  SQLSTATE[42S01]: Base table or view already exists: 1050 Table 'users' already exists  

一見Migration table created successfully.と出力されているから正常終了しているように見えるけど、作成されたテーブルをmysqlのshow tables;で確認すると、複数あるMigrationファイルの一つでエラーになって、すべてのMigrationファイルが実行されていないことがわかる。

こういうときは、storage/logs/laravel.logをチェックするとエラーとなっているmigrationファイルを特定できる。そして、普通に参照すると余計なエラーメッセージも出力されて一見わかりにくいので、以下のコマンドのように実行日付をgrepでパイプで渡してやると、ピンポイントで該当エラーファイルを特定できるのでおすすめ。

tail -200 storage/logs/laravel.log | grep 2017-02-01
[2017-02-01 20:38:02] local.ERROR: Symfony\Component\Debug\Exception\FatalThrowableError: Class 'Scheme' not found in /var/www/mice/laravel/database/migrations/2017_01_29_203457_create_mst_objectives_table.php:16
[2017-02-01 20:38:48] local.ERROR: Symfony\Component\Debug\Exception\FatalThrowableError: Call to undefined function create() in /var/www/mice/laravel/database/migrations/2017_01_29_203501_create_mst_lines_table.php:16

あと、途中までMigrationファイルで作成さたテーブルがある状態で、再度php artisan migrateをやるとすでに同名の既存テーブルがあるとエラーになってしまうので、php artisan migrate:rollback(一つ前にロールバック)かphp artisan migrate:reset(全マイグレーションロールバック)で重複テーブルがない状況にしてmigrateを再実行すれば良い。

マイグレーションと初期値設定 5.dev Laravel

migrationsテーブルに操作履歴が残っているので、このテーブルの中身を手動でdeleteするとphp artisan migrate:rollbackを実行しても戻せるロールバックがないというようなエラーになるので注意。そうなってしまって、不要テーブルを消すときはdrop table テーブル名で地道に消していくしかない。

エラーが出るとブログ書くネタができた!!、などと思いながら最近はいろいろやっているかな。

MySQLのroot権限がなくなったときの対処(mysqlデータベースが消えた)

MySQL(正確にはMariaDB)をvagrantの仮想環境サーバで使っていたのだけど、なぜかroot権限でログインしてもcreate databaseなどがアクセス拒否でエラーになった。正確なエラーは以下。

Access denied for user ‘root’@’localhost’ (using password: NO)

以下簡易メモ的に対処方を示しておく。詳細な実行結果も示したいが、iterm2のスクロールが該当箇所まで上に行かないので(次から自動ログ出力した)箇条書きで。
iterm2でターミナルログを自動的に取得させる | 俺的備忘録 〜なんかいろいろ〜

  1. show databases;でデータベースを確認するとなぜかmysqlが消えている(原因はこれ)
  2. service mysql stopでmysqlのサービスを停止する
  3. /var/lib/mysql/以下のファイルを全部削除
  4. sudo yum remove mariadbmariadbをアンインストール
  5. sudo yum install mariadb-serverでmariadbを再インストール(これだけで関連するものは全部インストールされるっぽい)
  6. service mysql startでmysqlサービスを起動
  7. mysql -u rootでログイン

そしてmysqlデータベースが存在することを確認。

MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| test               |
+--------------------+

なぜmysqlデータベースが消えたのかは謎・・・・。あと、再インストールした直後はmysqlがすぐに反映されなくて、少し時間をおいてrootでmysqlに再ログインすると反映された。これも謎。とにかく、解決できてよかった。