20191204のlaravelに関する記事は13件です。

Laravel6 ViewからControllerへ渡されたget/post データの取得の仕方 パターン整理

Laravelでviewからpost/get したデータをControllerで記述形式がいくつかあって、よくわからないので整理します。

とりあえず、思いつく限りでどんな記述形式がるか書き出してみます。
仮にview側でpostかgetでデータが送られたときに、3パターンくらい取得方法がある気がします。
記述形式はどれでもいいのかと思いきや、エラーではじかれる組み合わせがあるっぽいので整理します。

形式は1個でいい気がするけど、どうしていくつもあるのでしょう?
たぶん、整理していくうちに分かるかもしれません。

Controller
function example(Request $request){
 $request->input('name');
 $request->name;
 $request('name');
}

例えば、View側からPOST形式でデータを渡した場合

View
<form method="POST" action="/index">
   <input type="text" name="name">
   <input type="text" name="pass">
   <input type="submit">
</form>

コントローラー側で正しく動作する記述は

Controller
function example(Request $request){
 $request->name;
 $request->input('name');
}

の2つでした!

$request('name');

を使用すると

image.png
こんなエラーが出ちゃいました。

次にget形式ではどうなるか調べてみます。

View
<form method="GET" action="/index">
   <input type="text" name="name">
   <input type="text" name="pass">
   <input type="submit">
</form>

postの時と同じ結果になりました(笑)
ということは、単純に自分の勘違いで使えないコードを覚えてしまったようです。。
どうして、こんな勘違いをしていたのか?
それは2つの記述方法が、混同して記憶違いで直感的に書きやすい記述が刷り込まれてしまったからだと思います(言い訳)
結局、形式が2つある理由はよくわかりませんでした。
1つだけなら、勘違いすることもないのにと思いました。

Laravelでは
ページ表示にview()とredirect()があって、
どちらを使うと便利なのかわからずにいます。

redirect()は記述形式がroute()を使う場合と使わずに記述する方法があるので、
どれがどういうときに向いている記述なのか?
調べたいと思います

以上、勘違いしていましたという記事になりました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPカンファレンスに参加してきました!

PHPカンファレンスに参加してきました!

先日12月1日(日)にPHPカンファレンスに参加しましたので、
そのレポートというか感じたこと記事をまとめておきます。

PHP Conference Japan 2019

全体の感想

PHPに特化したというよりは、周辺の技術、アーキテクチャなど様々な部分のセッションがあり、
視野が広がるようなカンファレンスだったなと感じました。
と同時に自分より年下の人が話していたりと、
すごいなーとひたすら関心です。

個人的には「MVCにおける「モデル」とはなにか」が、
哲学的で面白かったと感じますし、
今までMVC=model:データベース/view:フロント/Controller:他全部
的なざっくりとして捉え方がどれだけ浅はかだったかを痛感致しました。。。

アーキテクチャ=インフラ構成のイメージが強かったのですが、
アプリケーションアーキテクチャとしてフレームワークをどのように扱うかの意識次第で
運用保守、改修のスピードに結構な差が出るという点を認識しなければと反省しました。
一発ではあまり理解ができなかったので、
今後も継続してインプットしようと思います。

参加したセッションとその記事(一部)

MVCにおける「モデル」とはなにか

トーク内容

日本の開発者の間でドメイン駆動設計の話題が盛り上がっています。
ドメイン駆動設計の話が出てくるたびに「モデルとはなにか」という話がでてきますが、多くの話は実装に寄った話になりがちです。

Model-View-Controllerパターン提唱者のトリグヴェ・リーンスカウク博士は「モデルは知識の表象である」と定義しています。
このトークでは、「モデル」とはそもそも何であるのかについて、リーンスカウク博士のModel-View-Controllerパターンを参照しながら詳しく検討します。開発以前にどんな基本的な問題が存在し、何を解決するべく「モデル」という概念は提示されたのか?についてお話します。

1言メモ

哲学的で面白かったですが、難しかった。。
感じたこと記事

PHPerのためのテストコード入門

トーク内容

皆さんは普段テストコードを書いていますか?もしくはこれから導入しようと奮闘していますか?はたまたテストコードというのを初めて聞きましたか?
テストコードと一口に言っても、ユニットテスト、featureテスト、APIテスト、E2Eテストなどたくさんの種類のテストコードがあります。
テストコードを導入する前に「なぜそのテストコードを書くのか」を話せることを目的として、テストそのものやテストコードについてお話ししていきます。

1言メモ

サテライト視聴していたのですが、
途中で切れてしまったので残念でした、、、
発表者が年下というのには尊敬の念しか抱きません。
感じたこと記事

プログラム未経験からたった3ヶ月で圧倒的な開発力を身につける ~ スクラッチ開発の重要性

トーク内容

数百以上のプロジェクト開発経験を踏まえ、プログラマに必要なスキルとは?
数年後、10年後先を見据えどのようなスキル・考え方を身につければ
プログラマとして圧倒的に成長できるか?

上記について徹底的に分析し、成長するためのエッセンスを凝縮した
教育カリキュラムを紹介いたします。

1言メモ

スピーカーの前田さんは1ヶ月で400時間ほど開発を行っていたそうです。
さすがに起業するレベルの人は熱量が違うなーと感じつつ、
今の自分は業務(8時間×20日)+ 自習(2h×30日)って感じなので、
全然足りないなーと反省もありました。

また、スキルエンジンというプログラミングスクールをやっているようなのですが、
そこでのフルスタックエンジニアには、
進捗・スケジュールの報連相といったタスク管理的な部分や、
思考体力、タイピングといった基礎体力も含めてできる人を指すとのこと。
で、この進捗・スケジュールの報連相といったタスク管理的な部分によって、
フリーランスなどでは仕事の取れ方が結構変わってくるとのことでした。
経験者ならではの考え方で参考になす。

時間の関係もあって、
技術的、具体的な話はあまりなかったのは少し残念でした。。

Laravel × クリーンアーキテクチャ 開発中の現状をお伝えしたい

トーク内容

「クリーンアーキテクチャ」とは、DDDの文脈における「ドメインモデル」をシステムの中心にすえ、「入力」「永続」「出力」などの多方面で抽象化を行うことで、高度に変化への強さを獲得できるアーキテクチャです。昨今の変化の早さに対応するべく「アジャイル開発」や「マイクロサービス」が叫ばれているように、「クリーンアーキテクチャ」もまた日増しにその注目度は上がっているように感じます。

しかし、やや取っつきにくいアーキテクチャであることからか、現状参考になる資料が無数に転がっているというわけでもありません。そこで、エキテンで現在Laravelベースで開発中の新しい予約システムを題材に、実際にアーキテクチャを形作っているコンポーネントの紹介や、開発する上で行ってみたちょっとした工夫、開発フェーズにおける現場の生の声などをお話しようと思います。

1言メモ

正直クリーンアーキテクチャとは?状態だったので、
言わんとする事はなんとなくわかるけど本質的には全然理解できていないといった状態でした。
クリーンアーキテクチャについてはさくっと理解できなさそうでしたので、
後日まとめようと思います。。。

オニギリペイのセキュリティ事故に学ぶ安全なサービスの構築法

トーク内容

安全なインターネットサービスを提供するにあたっては、ソフトウェアバグとしての脆弱性対策だけではなく、ビジネスモデル・サービス仕様・アーキテクチャ設計・ソフトウェア設計・実装・サーバーなどの多方面からセキュリティ検証が重要となります。本セッションでは、架空のQRコード決済サービス「オニギリペイ」のセキュリティ事故を題材として、サービスの企画・要件定義・設計・実装の各フェーズに起因する問題とその対策を具体的に紹介します。

1言メモ

概念的な話と、そこから具体的な方法論まで落とし込んでお話頂いたので、
今後どうすべきかがわかりやすかったです。
そういう意味ではある種どこで何をするかがふわっと把握していた状態であったセキュリティ対応というものに、
具体的にこのタイミングでこれをやるべきというのが理解できたなと感じました。

感じたこと記事

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【備忘録】storage/framework/cache のエラーでキャッシュを作り直した

EC2インスタンスを再起動したら、こんなエラーが。

[oauth/token][exception_message]file_put_contents(/Hogehoge/storage/framework/cache/data/9a/c1/9ac1b29cf1a1d991418507a9743d8479c3be0636): failed to open stream: No such file or directory

どうやら再起動したとしても、内部のキャッシュが残っててしまって、更新したプログラムをうまく反映してくれない時があるらしいです・・・。
キャッシュ系を全部クリアしてみてみたり、不要になったファイルを削除してみたりしてもうまくいかず。
最終的に作り直しました。

$ composer dump-autoload // composer.json の名前空間の割り当てマップの変更を autoloader.php に反映させるコマンド
$ php artisan clear-compiled // クラスローディングの最適化や、テストのために最適化させるコマンド
$ php artisan optimize // 最適化されたクラスローダを生成するコマンド
$ php artisan config:cache // configのキャッシュをクリアするコマンド

参考
https://getcomposer.org/doc/03-cli.md#dump-autoload-dumpautoload-
http://recipes.laravel.jp/recipe/36
http://recipes.laravel.jp/recipe/50
https://qiita.com/Ping/items/10ada8d069e13d729701

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

リポジトリパターンと Laravel アプリケーションでのディレクトリ構造

DMMグループ Advent Calendar 2019 4日目の記事です。

この記事では、弊チームの一部プロダクトで採用したリポジトリパターンについて紹介します。

リポジトリパターンについて

リポジトリパターンとはビジネスロジックとデータ操作のロジックを分離し、データ操作を抽象化したレイヤに任せるデザインパターンのことです。
リポジトリパターンでは、DBの操作や外部APIによるデータ取得等のデータソースへのアクセス部分は Repository インターフェースから完全に隠蔽されます。
そのため、アプリケーションはデータソースがDBであっても外部API静的ファイルであっても、それを意識することなくデータ操作を行うことができます。

ディレクトリ構造

今回採用したディレクトリ構造の例です。

app
|--Console
|--Exceptions
|--Http
| |--Controllers
| |--Middleware
| |--Requests
|--Models
| |--Article.php
| |--ArticleImage.php
| |--User.php
|--Providers
| |--AppServiceProvider.php
|--Repositories
| |--Article
| | |--ArticleRepository.php
| | |--EloquentArticleRepository.php
| |--User
| | |--UserRepository.php
| | |--DummyUserRepository.php
| | |--UserAPIRepository.php
|--Services
| |--ArticleService.php

Model クラスはデフォルトの場合 app 直下に設置されますが、Model が増えた際に視認性が悪くなるため、今回は models/ 配下に配置しています。

images.001.png

以下で各層の役割を説明します。

Service

この構造では Repository と Controller の間に、ビジネスロジックを管理するための Service を追加しています。
複雑なビジネスロジックを Service に切り出し、 Repository はデータの操作のみを行うように責務を分離することで、 Repository が複雑になることを防ぎます。

また、今回の場合だと Controller は外部からと外部への値の受け渡しのみを担っています。
しかし、ビジネスロジックがそれほど複雑でない場合は、 Service 層を実装せずに Controller に実装する方法でも良いかもしれません。

Repository

Repository 層は1つのインターフェースと1つ以上の Repository の実態で構成されています。

|--Repositories
| |--User
| | |--UserRepository.php
| | |--DummyUserRepository.php
| | |--UserAPIRepository.php

リポジトリパターンを使用することで、「特定の環境時は外部のAPIへの参照をダミーデータに変更する」「使用するDBを変更する」といった場合に対応しやすくなります。

例として、「通常は外部のAPIからユーザデータを取得し、テスト時はダミーデータを取得したい」という場合を考えてみます。

UserRepository はインターフェイスです。

UserRepository.php
interface UserRepository
{
    public function findUserByToken(string $token): User;
}

UserAPIRepository では外部のAPIから取得したデータを返しますが、 DummyUserRepository ではダミーデータを返すような作りにします。

UserAPIRepository.php
class UserAPIRepository implements UserRepository
{
    public function findUserByToken(string $token): User
    {
        try {
            $user = $this->getUser($token); // 外部APIからユーザー情報を取得する処理(省略)
            return $user;
        } catch(Exceptions $e) {
            throw new NoUserException();
        }
    }
}
DummyUserRepository.php
class DummyUserRepository implements UserRepository
{
    public function findUserByToken(string $token): User
    {
        $dummyUser        = User(); // Eloquent モデルではないただのクラス
        $dummyUser->id    = 1;
        $dummyUser->name  = 'ほげほげ';
        $dummyUser->token = 'abcd1234';

        if ($dummyUser->token === $accessToken) {
           return $dummyUser;
        }
        throw new NoUserException();
    }
}

AppServiceProvider で環境ごとに注入する Repository を変更します。

AppServiceProvider.php
class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        // テスト環境の場合はダミーデータ用の Repository に向ける
        if (App::environment('testing')) {
            $this->app->bind(UserRepository::class, DummyUserRepository::class);
        } else {
            $this->app->bind(UserRepository::class, UserAPIRepository::class);
        }
    }
}

これにより、テスト環境のときはデータの取得方法を変更することができます。

このようにインターフェースを介すことで、実際の操作方法を意識することなくデータの操作を行うことができます。

Repository と Model

Model と Repository は 1:1 とは限りません。

例えば、以下のような要件があったとします。

  • 「記事( articles )」には必ず「記事の作者であるユーザ( users )」がおり、記事の削除・更新は作者のみ可能
  • 「記事( articles )」には「画像( article_images )」が紐付いており、この画像は全くない場合もあれば複数ある場合もある

er.png

仮に、Article クラスと ArticleImage クラスに対してリポジトリ、 ArticleRepository と ArticleImageRepository を実装したとします。

「画像( article_images )」は「記事の作者であるユーザ( users )」を知らないため、画像を削除・追加しようとした場合に「記事( articles )」を経由して「作者( users )」を取得するようなロジックを ArticleImageRepository に記述する必要があります。
もちろんこのロジックは ArticleRepository にも必要なため、データの整合性を担保するためのロジックが複数の Repository に分散してしまうことになります。
これを防ぐために、関連するオブジェクト郡( Article と ArticleImage )を1つの塊として考えて、両方のオブジェクトを扱う ArticleRepository を実装します。

関連するオブジェクトを集約することで、ロジックの分散を防ぎつつデータの整合性を担保することができます。

|--Models
| |--Article.php
| |--ArticleImage.php
| |--User.php
|--Repositories
| |--Article
| | |--ArticleRepository.php
| | |--EloquentArticleRepository.php

おわりに

この記事では、弊プロダクトで採用したリポジトリパターンを紹介しました。

以下、まとめです。

  • リポジトリパターンを使用することで、アプリケーションはデータの操作方法を意識することなくデータ操作を行うことができる
  • Repository には複雑なビジネスロジックを記述せず、データの操作のみ行うよう責務を分離することで Repository の複雑化を防ぐ
  • 関連するオブジェクト( Model )は1つの塊として考え、1つの Repository に集約する

この記事を通してどなたかのお役に立てれば幸いです!

明日の DMMグループ Advent Calendar 2019 の担当は、 @arika_nashika さんです!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelアプリケーションリリースタイミングでやっておくべき Tips

この記事はLaravel #2 Advent Calendar 2019の3日目になります。
まだ投稿されていないようですので、投稿します。

背景

Laravelアプリケーションをリリースする際に、やっておくべきTipsを共有がてら。

アプリケーションをメンテナンスモードにする

アプリケーションがメンテナンスモードの時、ステータスコード503でMaintenanceModeException例外が投げられます。

$ php artisan down

メンテナンス中にアクセス許可するIPを指定する

メンテナンスモード中でも開発者は確認をしたいと思うので、アクセスIPを設定します。

$ php artisan down --allow=127.0.0.1 --allow=192.168.0.0/16

**Laravel5.5以前だとmiddlewareを編集してアクセスIP許可機能の人力設定が必要の記憶です。

Laravelアプリケーションを最新化する

masterにリリースするものが全部入っている前提です。

$ git pull origin master

マイグレーション実行

$ php artisan migrate

APP_ENV=production
だと確認メッセージがコンソールに出ます。

composer installを実行する

$ composer install --no-dev

** --no-devは必須です!つけないと開発環境用の余計なモジュールが入って、事故の原因となります。

フロントエンド関連のファイルを本番環境用にコンパイル

$ npm install
$ npm run prod

キャッシュを更新する

$ php artisan cache:clear
$ php artisan config:clear
$ php artisan route:clear
$ php artisan view:clear

アプリケーションを復帰させる

$ php artisan up

最後に

これらのコマンドをshellにまとめて実行すれば、ヒューマンエラーも減らせそうです。
CI/CDしている際は、これらを実行するようにすれば多分OKだと思います。

明日は@izayoi256さんの記事です!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

開発への異常な愛情 または私は如何にして嫉妬を止めてActive Recordを愛するようになったか

はじめに

こちらの記事は「tecotec Advent Calendar 2019」の5日目です。
当方フリーランスエンジニアなのですが、先月書いた記事: "Slackはただのコミュニケーションツールじゃない、企業の技術を映す鏡だ" がにわかにバズったことから、現在ご一緒にお仕事させていただいているtecotecさんに声をかけていただきました。ありがたい限りです。

ということで自分の中で温めていた、少々重苦しいテーマについて語ろうと思います。

この記事について

私は新卒で就職してから、王道のようでいて珍しいようでいて、それでいて激動のキャリアを歩んできました。

SIerでのレガシーシステム保守、データ分析・機械学習ベンチャー部門での便利屋、独立してwebエンジニア……

特にSIerでのレガシーシステム保守経験が、自分のキャリアの基盤であり、同時に今に至るまで引きずっている複雑な気持ちの根源となっています。

こちらの記事でActive Recordというデザインパターン(というよりもはやwebフレームワークの必須教養科目?)については、一切知らない人にも通じるように語りたいと思いますが、それはこの記事でメインとしたいことではありません。

それよりも、私のエンジニアとしての半生を語りながら

  • 10年単位で稼働している巨大プロジェクトが抱えやすい問題
  • Active Recordは「銀の弾丸」か

そして、もう一つ。

  • エンジニア間で生まれる断絶あるいは格差

という少々重苦しいテーマ群について、外堀から徐々に語ることを目的としています。
途中でJavaやPHPのコードに触れますが、特に前提知識は必要ありません。

あらかじめ謝罪しておきますが、この記事は明確な結論を持ちません。
それは私の中に明確な結論がないからです。
その理由だけは……明確に語らせて頂きますので、しばしお付き合いください。

SQLおじさんという言葉

ある時期の自分は、SQLの信奉者でした。

一部の方は、このことを聞いて「SQLおじさん」という言葉を思い浮かべられたかもしれません。

端的に言えば SQLおじさんとは「DBに関連するロジックは全てプログラムから切り離してDB側で処理すべき」 という強い思想に基づく記事を投稿した結果、議論を呼んでしまったとあるエンジニアを指して生まれた一種のスラングです。
ただ、元ネタとなった「SQLおじさん」関連の記事を調べる限り、この記事で扱うような「SQLを直接プログラムに書き込むようなコード」 を是としていた可能性は極めて低いものと思われます。

なのでこの記事では「SQL闇落ち系エンジニア」という言葉を使いましょう。……言葉のキレが極めて悪いですが許してください。

例えば、SQL闇落ち系エンジニアの世界に「ORマッパー」……データベースに対するアクセスをなるべく抽象化しよう = SQLというド直球なロジックはなるべく隠蔽しよう、という概念はありません。

「SQLを直にプログラムに記載すれば良いじゃないか?SQLは非常に強力なんだから」。

Laravel, Rails, Django。
そういった代表的web系フレームワークは「わざわざ」DBアクセス方法を提供してくれています(これがまさしく先程述べた"Active Record"です)。

「何の意味があるんだ? SQLがあれば自由自在にデータを取得できるのに」。

……少しでもWebフレームワークを勉強したことがある方なら、このような考え方にすぐ顔をしかめることでしょう。

ですが……
私はまさに、このような考え方の持ち主でした。
社会人経験3年に満たない25歳のエンジニアでしたが、あの頃の私はまさに「SQL闇落ち系エンジニア」でした。

例えばRailsのような比較的モダンなwebフレームワークを利用した環境からからエンジニアとしてデビューされた方々に、上記の考え方は理解ができないと思います。

でも、知ってほしい。
「何故そのようなエンジニアが生まれるのか」。
言い換えれば。
「エンジニアはどのようにして闇に落ちるのか」。
そのために、改めまして私のSIer時代から語らせていただきたく。
どうぞよろしくお願いいたします。

本題

エンジニアとしてのデビュー

私は2015年4月、新卒未経験で受託開発を中心とした100人規模の会社に就職いたしました。その規模の会社を "SIer" と呼称することが適当かどうかは別にして、その会社のビジネス形態は確実に "SIer" と呼ばれるようなものでした。
受託開発中心の割に元請け率が多いことを売りにしていたその会社はなかなかの歴史があり、大規模な案件に元請けとして長期期間関わっているということも多々ありました。

その中の一つに私はアサインされます。
製造業保守案件でした。

詳細はあまり言いたくないのですが、製造業とまとめられる中でもおそらく最大規模に属するであろう、超巨大なカテゴリの「製造」を扱っていました。

早速実際の業務のお話に入りたいのですが……
皆様はBOMという言葉を聞いたことがあるでしょうか。
爆弾(BOMB)ではありません。Bills of materials、部品表の略です。

私が携わったのは、BOM……Bills of materialsの管理アプリケーションの保守業務でした。

BOMは日本語でこそ「部品表」ですが、製造業における「部品表」はまさに製造の「心臓」です。
最も重要な器官の一つであり、同時に常に鼓動し、部品という血流を流し続ける、その中心地点です。
設計・生産はすべてこの"BOM"のCRUDをトリガとして流れていると言っても過言ではありません。

まぁ要するに。
とても重要で、同時に……

とても。極めて。
複雑だということです。

Javaの黎明期から開発・保守・改修が続けられてきたそのコードが有するクラスは1000に至り、テーブルの総量は100を軽く超えました。一つの機能が「中心的にアクセスするテーブル」の数だけで数十に至ることもザラにありました。

全貌を把握することは、不可能。
中心の業務知識とシステムの仕様を身につけるだけでも、1年では全くの不足。

……そんなシステムの保守・改修に1年目の私はアサインされたわけです。
そして2年半、会社に辞意を告げるまでそのアサインは続きました。

圧倒的に信頼できる「力」

エンジニアにとって「ググる」力は非常に大事です。ググるための情報源を大量に持っていること、stackoverflowの英語に臆さないこと、面倒くさくてもとりあえず公式ドキュメントを読み込むこと、気分転換に@yametaroさんのQiitaを読むこと……
非常に大事です。

さて、そんなエンジニアライフの1年目。
私の現場にはインターネットが通っていませんでした。

否、厳密に言えば通っていたのですが、数人のメンバで7Gの帯域をpocket wifiで共有していたため、あまりマトモに使えなかったのです。
これは2年目に私が総務にゴネてpocket wifiの量が物理的に増えたことで改善いたしました。

仮にそうでなかったにせよ、Java黎明期から受け継がれた某マンモス級ベンダー独自のフレームワークに支配された非常に難解・複雑なそのソースコードは、ネットに落ちている知識程度でどうこうなるものは非常に少なかったです。

それなのに会社の評価基準に「自分で調べられること」のような項目があったので評価面談時にキレていた記憶があります。今や懐かしい記憶です。良い思い出とは言えませんが。

また、転職活動時や職務経歴書を書くに即して「フレームワークの利用経験」を問われるというのがひどいコンプレックスでした。ググれば情報があるフレームワーク利用よりはるかに複雑な(と少なくとも当時の自分は思っていた)自分の経験は、転職市場での価値は低い。とにかく悔しかった。

……かなり話が逸れましたが、最後に語りたいテーマに繋がってくるので今しばらくお付き合い頂けますと幸いです。
話を戻します。

そんな「ググる」があまり意味をなさない環境の中、ただ一つ、ずっと変わらずそこにあって、ずっと変わらず重要な「技術」がありました。

お察しいただいている通り、それが……SQLでした。
それは私にとって圧倒的に信頼できる「力」でした。

複雑化するSQL、肥大する全能感

製造業保守業務の中には、「部品表リストの出力」というものもありました。

数百行のSQLを書き、出力し、加工し、提出する。

現在私は個人事業主としてデータ分析や機械学習案件のコンサルもスポットで進めているのですが、その基盤の、さらに基盤にあたる技術は確実にここで養われました。

SQLは、本当に凄まじいツールです。
SQL実践入門──高速でわかりやすいクエリの書き方 (WEB+DB PRESS plus)
あたりを是非お読みいただきたいのですが、卓越したSQLは芸術の域に達します。マジで。

例えばこちらの記事にあるように、SQLの GROUP BY 句には列名だけでなく式も記述することができる ことを初めて知ったときの衝撃は凄まじいものでした。

そうして私はどんどんSQLの技術を上げていき、数百行に至るSQLに立ち向かうことも多々ありました。

また、同時に……

私にとって。
SQLは。
必然的に。

ソースコードに直接記載されるものでもありました。

そのシステムのソースコードが、ずっと、そうであったように。

実際にどのようなコードだったかと言うと、こちらの記事にあるようにPreparedStatementを利用したソースコードでした。

tmp.java
Connection con = DriverManager(hoge,hoge,hoge);
String sql = "select name from hogeData where id = ?";
Statement st = con.preparedStatement(sql);
st.setInt(1, 1);
ResultSet rs = st.executeQuery();

クエリの結果……レコードの集合体は変数:rsに格納され、複数行取得されることが前提なので適宜ループで取得します。

この方法自体は未だに企業向けのJava研修等で教えられるものであり、それ自体は決して悪いものではありません。
Connectionの取得は共通化して隠蔽した上で、この方法とほぼ同様にResultSetに対する直接的なアプローチでデータをやり取りしているソースコードは、この世界に大量にあることと思います。

繰り返しますが、決してそれ自体が悪いということはありません。

しかし。
この方法にはいろいろな「時限爆弾」が仕掛けられているのです。
特にJavaやOracleを導入するような大規模案件で爆発しがちな、強烈な爆弾です。

そのことを記憶に留めて頂いて、先に2つ目のキーワードについてご説明させてください。

Active Recordとの出会い

さて、少し時系列が飛びます。

その現場を離れた後、会社でも悪目立ちしていた私は辞意表明とほぼ同タイミングでベンチャー部門に勧誘され、結果的に退職・独立を延期することになり、営業支援やらコンサルやらAWS運用やらLaravel炎上案件やらを経験し、その後独立。

Vue.js × Laravelというカリカリの自社開発BtoC、Web系案件を経験します。

ここで Active Record という私の価値観を壊す存在と出会うわけです。

この案件に携わる前からLaravelに二度ほど触れたことはありましたが、片方はAWS APIアクセスをメインとした少し特殊なソースコード、もう片方は……
もうなんというかMVCではなくVCになっており、CにSQLがベタ書きされていたような案件でした。アレはこの世の地獄でした。
そのため、Active Recordという考え方に出会うまで、少しタイムラグがあったわけです。

話を戻します。

Active Record……それは端的に言えば、「DBのレコード1行を1オブジェクトとして扱おう」という発想に基づく技術です。
端的に言って、

  1. レコードを完全にプログラム上の「オブジェクト」として扱える
  2. DBと密結合しやすいロジックの強力な「隠蔽」

という2つのメリットがあります。
具体例を踏まえて説明しましょう。
LaravelのModel関連は非常に機能が多いのですが、分量の都合上かなり端折ってお伝えすることになってしまうことをご容赦ください。

例えば、Webアプリに登録されたユーザーを管理する「users」テーブルがあるとします。

「あるとします」……とか言いましたが、Laravelではこのusersテーブルはコマンド一つでログイン処理も含めて自動生成されたりします。
詳細に関してはLaravel authあたりのキーワードで検索してみてください。

さて、そんなこんなでユーザー情報を管理するusersテーブルがこの世に生まれ落ちます。
Userのデータがたくさん入っているので、複数形でusersテーブルです。

そして、その中の1行が1クラスによって表現されるのがLaravelにおけるModel1の特徴です。
つまりクラス名は単数形となり、User.php となります。

User.php
class User extends Authenticatable
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'firstname', 'lastname', 'email', 'password',
    ];

    // Mutator
    public function setPasswordAttribute($value)
    {
        $this->attributes['password'] = Hash::make($value);
    }

    // Accessor
    public function getFullNameAttribute($value)
    {
        return $this->firstname . ' ' . $this->lastname
    }

    // Relation
    public function skills()
    {
        return $this->hasMany('App\Models\UserSkill');
    }

    // Other methods
    /**
     * ユーザーに対するメール送信を実行する
     *
     * @param $request
     * @return void
     */
    public function sendEmail()
    {
        // 何らかの処理
    }
    ......
}

何のことやら意味がわからないかもしれませんが、説明いたしますのでご安心ください。

例えば、usersテーブルに登録された全ユーザーを取得するためには以下のようなコードを書きます。

sample1.php
$users = User::all();

こうすると変数:$users にはUserインスタンスの集合体(LaravelのCollectionインスタンス2) が格納されます。
id:1番のユーザーだけ見つけたいなら、こうです。

sample2.php
$id = 1;
$user = User::find($id);

引数には主キーを渡すことができます。
この場合、$userには当然、Userインスタンスが格納されます。

さて、とにかくこれでid:1番のレコードを表す$userが取得できました。
このオブジェクトを操作することは、すなわちこのレコードを操作することになります。

例えばこのユーザーのパスワードを強制的に変更してみましょう。

sample3.php
$user->password = 'newpassword';
$user->save();

はい、これで完璧にパスワードがupdateされます。

「平文じゃん!インシデントじゃん!」と思われましたか?
でも大丈夫なんです。
User.php を確認してみましょう。

User.php
    // Mutator
    public function setPasswordAttribute($value)
    {
        $this->attributes['password'] = Hash::make($value);
    }

どこからpasswordにアクセスしても、セットする際には必ず暗号化するようになっています。
こちらはパスワードの暗号化を「一切意識する必要がありません」
SQLの関数で暗号化するか、プログラム側で毎回暗号化するか。
そんな面倒なことはModelへの定義一発で全て解決します。

これはオブジェクト指向で言うところの「セッタ(Setter)」の機能です。
LaravelのModelではMutatorと呼ばれています。

では逆に「ゲッタ(Getter)」はあるのでしょうか。
もちろんあります。Accessorと呼ばれています。
しかも滅茶苦茶強力です。

テーブル側にカラム:firstnameとlastnameがあるとします。
冗長なのでその場合、テーブルでfullnameは確実に持たないですよね?

でも、このように……

User.php
    // Accessor
    public function getFullNameAttribute($value)
    {
        return $this->firstname . ' ' . $this->lastname
    }
sample4.php
$user->firstname;
$user->lastname;
$user->fullname;

DBのカラムであるか、自前で作ったAccessorを経由して取得した属性か。
これら全て一切意識する必要がなくアクセスできるようになります。

さて、ここまでSELECTやUPDATEがありましたが、一切SQLが登場していません。
では、joinのような複雑な構文はどうでしょう?

これもまた非常に優れています。
例えば、Webサイトが転職サイトだったとして、ユーザーのスキルを記録する別テーブルがあるとしましょう。
user_skills3というテーブルです。
user_skillsはカラム:user_idを持ち、 usersの主キー:idと結合し、ユーザー1:スキルNの関係であるとします。

User.php
    // Relation
    public function skills()
    {
        return $this->hasMany('App\Models\UserSkill');
    }

このように定義すれば(厳密にはUserSkill側のモデルも定義する必要があるのですが)

sample5.php
$skills = $user->skills;

これでjoin等を一切書くことなく、UserSkillsインスタンスの集合体(Collection)が取得できました。

さて。
ここまで頑なにSQLを登場させないことのメリットは何なのでしょうか?

先述したとおり、ひとつ目は

  • レコードを完全にプログラム上の「オブジェクト」として扱える

です。
オブジェクト指向の
「抽象化されたオブジェクトに対して、命令して、振る舞わせる」
という考え方を知っていれば、その考え方に極めて忠実にコーディングが進められます。

例えば、このユーザーに対してなんらかのメールを送りたい、という処理も非常に簡単です。

User.php
    // Other methods
    /**
     * ユーザーに対するメール送信を実行する
     *
     * @return void
     */
    public function sendEmail()
    {
        // 何らかの処理
    }
    ......
}
sample.php
$user->sendEmail();

これならControllerに書くことになってもスッキリです4
そしてもう一つの大きなメリット、それが……

  • DBと密結合しやすいロジックの強力な「隠蔽」

です。
「DBと密結合しやすいロジック」の代表例がまさにSQLです。

これに関しては保留していた「SQLベタ書きコードに仕組まれた時限爆弾」について語る方がわかりやすいでしょう。

「SQLベタ書きコード」に仕組まれた時限爆弾

さて、もうとっくにお忘れと思いますが、こちらのコード。

tmp.java
Connection con = DriverManager(hoge,hoge,hoge);
String sql = "select name from hogeData where id = ?";
Statement st = con.preparedStatement(sql);
st.setInt(1, 1);
ResultSet rs = st.executeQuery();

こちらのコードが、大規模なアプリケーションに組み込まれることを考えてみましょう。

例えば。例えばです。ホントに例えば。

「超複雑なデータのリストを表示して欲しい」という要望があったとします。
超複雑なデータは20種類のテーブルを極めて複雑な条件で結合することによって得られます。
(そんな要件あらんやろと思ったあなたは幸せです)

tmp.java
String sql = "
select
    TABLEA.A,
    TABLEB.B,
    TABLEC.C,
......
    from
・・・・・・・
    以下500行以上
";
Statement st = con.preparedStatement(sql);
ResultSet rs = st.executeQuery();

はい。

さて。このコード、どこに実装します?
Utility的な共通処理を作って、そこにメソッドを定義する。一つの手段ですね。
(Java書くことから2年以上離れているので、もしコード部分に誤りありましたら遠慮なくご指摘ください)

Utility.java
public class Utility {
    public ReultSet executeSuperQuery(Connection con){
        String sql = "
        select
            TABLEA.A,
            TABLEB.B,
            TABLEC.C,
        ......
            from
        ......
            以下500行以上
        ";
        Statement st = con.preparedStatement(sql);
        return st.executeQuery();
    }
}

ただ、もしこの方法を用いた場合、Utilityクラスは
executeSuperQuery2,
executeSuperQuery3,
executeSuperQuery4,
executeSuperQuery5,
……
のように容赦なくメソッドが定義されていき、最終的な記述量は人間の限界を超えるでしょう

ということで、適当なクラスに分離しましょうという発想になります。

SuperSQLExecuter.java
public class SuperSQLExecuter {
    public ReultSet executeSuperQuery(Connection con){
        String sql = "
        select
            TABLEA.A,
            TABLEB.B,
            TABLEC.C,
        ......
            from
        ......
            以下500行以上
        ";
        Statement st = con.preparedStatement(sql);
        return st.executeQuery();
    }
}

さて。
この "SuperSQLExecuter" とは、何者なのでしょうか?

オブジェクト指向自体議論が分かれる概念ではありますが、「あらゆる概念・役割・機能を抽象化して、クラス単位で表現しよう」という発想にそれほど大きな差異はないはずです。
しかし、この「SuperSQLExecuter」 のインスタンス……つまり「実態」は、根本的に「何」を表現しているのでしょう?

Active Record、あるいはModel、あるいはCollectionが「何」を表しているか。
それは非常に明瞭で、レコード or レコードの集合体です。

一方、こちらはどうも判然としません。
「SQLを実行したい」という手続き的発想があって、それを強引にクラスとしてまとめたような……
そう、結局 "Utility" 的な発想から逃れられないわけです。

それ自体は悪いことではありません。
世の中に数多く存在するライブラリはほぼそのような発想で成り立っています。

問題は、SuperSQLExecuter はゴリッゴリにビジネスロジックのメインを担っているということです。
ゴリッゴリです。

例えば「見る人に応じてSQLの結合条件を変更したい」という要件があったとしましょう。

SuperSQLExecuter に定義されたSQLはif文での分岐が加わり、この時点で難読化します。
別SQLに分離することもできますが、それはそれでクラスの分量が純粋に極めて大きくなり、また人間の限界を超えていきます。
あるいはSQLだけを返すようなメソッドに分離するか。
……それはそれで正しいのですが、また新たな問題を生みます。

それは、このメソッドの戻り値が結局「どのような情報を持っているか」を知りたいとき、どうすればよいか。
結論として「中に埋め込まれているSQLを読み込むしかない」という頭を抱えたくなる状況が生まれます。
でも中でprivateメソッドが何度も呼ばれた結果発生したSQLなど、どのように辿れば良いと言うのでしょうか。
追々、外部ドキュメントに頼らざるを得ないような状況が生まれがちです。

LaravelのModelであれば、テーブル定義とAccessorをざっと見れば確実にわかります。

この混沌とした状況は、結局のところ「SQLというフリースタイルな構文を、フリースタイルなメソッドがラッピングしている」ということに起因します。
プログラミングを語る上で「隠蔽」という言葉はかなり色々な意味合いで使われており、例えばオブジェクトのメンバ変数を気軽に更新できないような実装にすることを「カプセル化」と呼び、カプセル化によって成し遂げられていることを「隠蔽」と呼びます。
ただ、この場合の「隠蔽」は、どちらかと言えばより根源的な意味合いでの「隠蔽」です。

SQLが言葉通り「隠蔽されない = 常に更新されるファイルに定義され、自由自在に改修されてしまう状態に置かれる」場合、ここで示してきた通り収拾がつかなくなります。
ここまでに示したとおり、悪い意味で何でもできちゃいますから。

SQLの実行結果をオブジェクト単位でもし扱えるなら、そしてオブジェクト内のデータに対するアクセス方法が限定されているなら、ここまで混沌とした状況は生まれにくいはずです。
Active RecordはSQLを高度に「隠蔽」し、個別のメソッドに細分化することで、「収拾がつかなくなること」をなるべく防いでくれています。

ただ、現実として……一度難解なSQLをプログラム側に定義してしまった現場では、無情にもSQLそのものに対する改修は積み重ねられていく。

さて。
そんなこんなで、SuperSQLExecuter が極めて巨大になったり、複雑になった頃合いに、顧客から一言、こう言われるわけです。

このリスト……更新できるようにしてくれない?

……繰り返しますが、実話じゃないですからね。念のため。
(現実はもっと複雑ですから……)

隠蔽と疎結合

結局、このような悲劇がなぜ起こるのかと言えば「密結合だから」という一言に尽きるでしょう。

プログラム側に、DB側の機能である「SQL」が接着剤で張り付いているかのように張り付いてしまっている。
そして分離できなくなってしまう。

Active Recordにおいては「SQL」は高度に隠蔽されています。
それによって実現されるのは「疎結合」であると言えます。

説明するまでもないかもしれませんが、「疎結合」はイメージとしてはマジックテープでくっついている状態、「密結合」は瞬間強力接着剤でくっついている状態です。
「疎結合」なら、テストの際などに切り分けて、別々に検証することが出来ます。

その観点から言えば、Active Recordは

  • アプリケーションからみて外部の存在であるDBに関わる情報がすべてオブジェクトの内部にラップされ
  • オブジェクトのメソッド、あるいはインスタンス変数へのアクセスという形で行えるようになり
  • また, 十分うまく設計できているならばテスト時にはモックオブジェクトに差し替えることでDBから 完全に分離した形で単体テストもできる

という「疎結合」を導きます。
参考:Active Record Sucks! (あるいはSQLおじさんの憂鬱)
……ただ、実のところ「疎結合」「密結合」という概念は簡単なようでいて、気軽に語るには極めて難しく、実際上記の参考記事も「Active RecordはDBと密結合だ」と批判的な結論を導いています。
これは「何と何が結合しているのか」「何と何を結合させるべきなのか」という観点が様々だからです。

ただ、少なくとも言えることは……
コードへのSQL直記載は「ド密結合」ということです。

それでも、仮にそのことを理解していたとしても、「闇落ちエンジニア」だった頃の私は……
SQLを直に書くことをやめなかったでしょう。
むしろ、Laravelに初めて触れたとき「SQLを……書かせろ……」と荒ぶりそうになったことを覚えています。

長々とお付き合い頂き、ありがとうございました。
ようやくこの記事は核心に入ります。

それでも自分はやり方を変えなかった

多分、「滅茶苦茶頑張れば」、最初に配属されたその現場にORMを導入することもできたのだと思います。
でも、そもそもそんな発想に至るわけがありません。

ソースコードはコメントによる履歴の追記まみれ、「なるべく革新のない」コードを求められました。
deployはかなり特殊なツールを使って、1時間以上かけて行っていました。
ファイルの差分はExcelで管理していました。

10年以上の単位でそうやって動いている現場で、ORMやら、Gitホスティングによるコードレビューやら、そんな技術を導入するのは工数的ハードル以上に、心理的ハードルから不可能と言っていいでしょう。

むしろ自分は「それこそが普通だ」と心から信じていましたから。

それでも自分の中にあった閉塞感と、違和感、何より漠然とした強烈な嫉妬心は無視しきれなかったわけです。
何に対する強烈な嫉妬心か?

Qiitaに投稿するようなエンジニアに対する、嫉妬心。
あるいは開発を愛せるエンジニアに対する、嫉妬心。

だから私はその現場を中から変えることではなく、独立を選びました。(結果的にベンチャー部門に移動し、1年経験を積んでから独立という流れになりましたが……)

「自分のようにどんどん若手エンジニアは独立しよう」。
それこそがこの記事の結論……
だったら、どれだけ楽だったか。

冒頭に述べた「明確な結論が自分の中にない」。
その意味について語らせてください。

Active Recordは銀の弾丸か

今、自分のスキル・知識はあの現場にいた頃と比較になりません。
では今、自分があの現場に戻ったとして、何もかも自由にできたとして。
「Active Recordを何らかの方法で導入できるか?できたとして、するか?」

無理です。
絶対に無理です。
絶対に、しません。

数十のテーブルを極めて複雑な条件で結合する。
そんな要件に対して、Active Recordは弱いです。
そもそも、OracleとJavaで構築されているようなシステムは、業務要件が極めて複雑で、それに応じた実装をするだけで必死ということも多々あります。

そんな必死で作られたシステムが、10年の時を経て、様々な「新人エンジニア」に揉まれながら、人の手の及ばない規模のシステムになっていく。
日本にはそんなシステムが数え切れないほど存在しています。
そんなシステムには、マイクロサービス的思想を含むモダンな開発技法をいくら持ち込んだところで、手も足も出ません。
ゼロベースで作り直せるなら別ですが、そんな余力がある大企業はまずないでしょう。

結局、「頑なに自説を曲げない」エンジニアライフを選ばざるを得なかったあの頃の自分は、あの頃の環境の中で、「正しかった」わけです。

便宜上「SQLを直書きするようなコードよりActive Recordの方が正しい」というような流れで、この記事は書きました。
でも、本当は違うのです。
「そもそもActive Recordは様々な欠点を抱えている」。
それも散々議論されていることですが、そちらもここで語りたかったことではない。

この記事で最も言いたかったこと。それは。
「Active Recordの利用を議論できるような現場が、すでに圧倒的に恵まれている」なのです。

ならどうするのか。
「そのような現場に行くために転職する」ことが正解なのか。
じゃあ、世に数多あるレガシーシステムたちはどうなる……?

もちろん自分が関わらなくても、なんだかんだ、いろんな人が携わって守っていきます。
社会は、技術会社はそうやって回ります。
そして何より、あの現場は自分を育ててくれた親のようなものです。それを否定することは、……「そんな現場はさっさと辞めるべきだ」ということは。
自分自身の否定に他ならない。
それは自分にはできない。

でも、最終的に自分は独立し、今はAWS・Lambdaを利用したサーバレスAPI構築や、S3へのSPAのホスティング等、比較的新しい開発に携わっています。
自分は今、それなりに楽しい。
これは私の、私の人生にとっての「正解」だったというだけの話です。

一方。
時折思い出すのです。

社会人2年目、Qiitaを読みながら「なんでこの人たちはそんな開発について楽しそうに語れるんだ……?」と嫌悪感にも似た感情を抱いていたことを。
社会人4年目、別案件に移動してから、Web系経験が深く、大規模案件の経験は比較的浅い上司が「なんだかんだシステムは1ヶ月もあれば仕様は把握できる」と言ったときに、「いや、いくらなんでも舐め過ぎです!」と怒ったことを。
どんどん新しい技術を導入する先輩に、激しい抵抗を抱いていたことを。

そして2年程度でエンジニアを辞めていった人たち。
「自分には技術がない」と、そう自嘲するエンジニアたち。

「現場に文句を言うべきではなく、現場を変えていくべきだ」という、「正論」と、その正論が想定する範囲が極めて狭いことに対する、激しい怒り。やるせなさ。
「あの世界」を見たエンジニアと、見ていないエンジニアの断絶。
「自分は開発を愛している」と言えるエンジニアと、そうでないエンジニア。

それら、全部を抱えた上で。

「Active Recordを、ORMを導入すれば、どんな現場でも幸せになれるよ」。
そんなことが言えたら、どれだけ楽だったか。
でも、口が裂けてもそんなことは言えない。

自分はあの世界に対して、何もできなかった。
それは仕方がないことだった。
その中で確実に言えることは……

エンジニア間の断絶、格差。
それらは、想像を遥かに絶する残酷さを帯びています。
それを解決する手段を、思想を……自分はまだ、持ち合わせていません。

それが冒頭に「明確な結論が自分の中にない」と述べた理由です。

終わりに

今、tecotecさんと楽しくお仕事させていただいてます!
という極めてAdvent Calender向きの内容を書かせて頂きました!
いやー、tecotecさん!ありがとうございます!!5

では、またどこかでお会いしましょう。


  1. DBのテーブルに対応するActive Recordの"Model"と MVCの"Model"は微妙に意味合いが違うという議論もあるのですが、ここでは省略いたします。 

  2. https://readouble.com/laravel/5.8/ja/collections.html 参照。厳密にはこちらのクラスを継承したIlluminate\Database\Eloquent\Collection になります。 

  3. Laravelではテーブル名や関連テーブル名、主キー・外部キーの命名にも厳密なルールがありますが、詳細は省略させていただきます。ご容赦を。 

  4. RepositoryPattern適用してさらにアクセスを抽象化すべきでは?みたいな話も省略させてください。あと、もっと細かい話をするとメソッドは主語→動詞のネーミングにしたいので、目的語であるuserが先に来るのは少し違和感がありますが、そこもご容赦を…… 

  5. ……色々すみません……これからもよろしくお願いいたします…… 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

とりあえず適当に本番データをローカル環境で入れることが出来るDB:seedを考えた

概要

以下のニーズに答えた

  • 本番環境のデータをそのままローカル環境に流し込みたい
  • DB構造が変わるたびにSeeder書き換えるのが嫌
  • 適当に入ればいい(適当とは下記3の方)

1 ある条件・目的・要求などに、うまくあてはまること。かなっていること。ふさわしいこと。また、そのさま。「工場の建設に適当な土地」「この仕事に適当する人材」
2 程度などが、ほどよいこと。また、そのさま。「調味料を適当に加える」「一日の適当な仕事量」
3 やり方などが、いいかげんであること。また、そのさま。悪い意味で用いられる。「客を適当にあしらう」「適当な返事でごまかす」

適当(テキトウ)とは - コトバンク

〜 想定する流れ 〜
1. データベースからcsvファイルでエクスポート
2. laravelの storage/db_csv に設置
3. php artisan DB:seed 実行

今回は1行目(カラム名が記述される)を含むcsvファイルをエクスポートしました。

環境

laravel 5.8
MySQL 5.7

つかった技術

CSVファイルを読み込む

SplFileObject クラスを利用して読み込む

$file = new SplFileObject("storage/db_csv/{$tableName}.csv");
// オプションをつける
$file->setFlags(
    \SplFileObject::READ_CSV |
    \SplFileObject::READ_AHEAD |
    \SplFileObject::SKIP_EMPTY |
    \SplFileObject::DROP_NEW_LINE
);

オプションについて

// CSV 列として行を読み込む
SplFileObject::READ_CSV

// 先読み/巻き戻しで読み出す
SplFileObject::READ_AHEAD

// ファイルの空行を読み飛す。READ_AHEADが有効であることが必須
SplFileObject::SKIP_EMPTY

// 行末の改行を読み飛ばす
SplFileObject::DROP_NEW_LINE

DBの情報を取得する

テーブルの一覧を取得

foreach (DB::select('SHOW TABLES') as $table) {
    $dbName = config('database.connections.mysql.database');
    $tableName = $table->{'Tables_in_' . $dbName};
}

カラム名とその型のリスト

$columns = Schema::connection('mysql')->getColumnListing($tableName)

注意

  • json型非対応
    • これを使って対応出来るとは思うのですが、とりあえず非対応です。そのうちがんばります。

コード

<?php

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;

class DatabaseSeeder extends Seeder
{

    /**
     * storage/db_csv に{テーブル名}.csvを設置する
     */
    public function run()
    {
        foreach (DB::select('SHOW TABLES') as $table) {
            $dbName = config('database.connections.mysql.database');
            $tableName = $table->{'Tables_in_' . $dbName};

            // migrationsは除外
            if ($tableName === 'migrations') continue;

            // 存在しなければ除外
            if (!File::exists("storage/db_csv/{$tableName}.csv")) continue;

            // もう何か入っていれば除外
            if (DB::table($tableName)->count() > 0) continue;

            $this->saveByCsv($tableName);
        }

    }

    /**
     * csvからdbへ保存
     * @param $tableName
     */
    private function saveByCsv($tableName)
    {
        $file = new SplFileObject("storage/db_csv/{$tableName}.csv");
        $file->setFlags(
            \SplFileObject::READ_CSV |
            \SplFileObject::READ_AHEAD |
            \SplFileObject::SKIP_EMPTY |
            \SplFileObject::DROP_NEW_LINE
        );

        // 存在するカラムを取得
        $columns = Schema::connection('mysql')->getColumnListing($tableName);

        $columnTypes = [];
        foreach ($columns as $column) {
            $columnTypes[$column] = Schema::connection('mysql')->getConnection()->getDoctrineColumn($tableName, $column)->toArray()['type'];
        }

        $records = [];
        foreach($file as $key => $line) {
            // 1行目はカラム名なので除外
            if ($key === 0) continue;

            $record = [];
            foreach ($columns as $columnKey => $column) {

                // 存在しない場合は適当に値を入れる
                if (empty($line[$columnKey]))
                    $line[$columnKey] = $this->columnTypeToDummyValues($columnTypes[$column]);

                // nullが文字列になっているので修正
                if ($line[$columnKey] === "NULL")
                    $line[$columnKey] = null;

                $record += [$column => $line[$columnKey]];
            }
            $records[] = $record;

        }

        DB::table($tableName)->insert($records);

    }

    /**
     * カラムのタイプ毎にダミーの値を返す
     *
     * @param $type
     * @return mixed|string
     */
    private function columnTypeToDummyValues($type)
    {
        $ColumnTypes = [
            'Doctrine\DBAL\Types\StringType' => 'ダミーテキスト',
            'Doctrine\DBAL\Types\IntegerType' => 1,
            'Doctrine\DBAL\Types\SmallIntType' => 10,
            'Doctrine\DBAL\Types\DateTimeType' => \Carbon\Carbon::now(),
            'Doctrine\DBAL\Types\TextType' => 'ダミーテキスト',
            'Doctrine\DBAL\Types\BooleanType' => true,
            'Doctrine\DBAL\Types\DecimalType' => 0.1,
        ];
        $text = '';
        foreach ($ColumnTypes as $ColumnType => $value) {
            if ($type instanceof $ColumnType) {
                $text = $value;
            }
        }
        return $text;
    }
}

おわり

とりあえず適当に入ります。
エラーが怖くて空の時はダミーデータ挿入するようにしていますが、動いていないようです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

laravelのmigrateができなかったときのメモ

SQLSTATE[HY000] [2054] The server requested authentication method unknown to the clientみたいなエラーを解決できたのでそのメモ。

とりあえずマイグレーションさえしてくれればよかったのでrootで動かしてみました。

mysql> select User, Host, plugin from mysql.user;
+------------------+-----------+-----------------------+
| User             | Host      | plugin                |
+------------------+-----------+-----------------------+
| mysql.infoschema | localhost | caching_sha2_password |
| mysql.session    | localhost | caching_sha2_password |
| mysql.sys        | localhost | caching_sha2_password |
| root             | localhost | caching_sha2_password |
+------------------+-----------+-----------------------+

rootユーザーの認証方式をcaching_sha2_passwordからmysql_native_passwordへ変更したらマイグレーションできました。

alter user root@localhost identified with mysql_native_password by "secret";
mysql> select User, Host, plugin from mysql.user;
+------------------+-----------+-----------------------+
| User             | Host      | plugin                |
+------------------+-----------+-----------------------+
| mysql.infoschema | localhost | caching_sha2_password |
| mysql.session    | localhost | caching_sha2_password |
| mysql.sys        | localhost | caching_sha2_password |
| root             | localhost | mysql_native_password |
+------------------+-----------+-----------------------+
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】クエリビルダでWhere句にA OR (B AND C)を書く

使用する機会があり、メモ程度にまとめてみます。

環境

PHP : 5.6.30
MySQL : 10.1.21
Laravel : 5.1.46

テーブル

以下サンプルテーブルです。

id name created_date updated_date
1 Suzuki Taro 2019-01-01 12:00:00 2019-01-01 12:00:00
2 Tanka Hanako 2019-01-25 10:00:00 2019-01-29 12:00:00
3 Takahashi Jiro 2019-02-01 16:00:00 2019-02-01 16:00:00

元のSQL

SELECT name FROM users
WHERE (created_date BETWEEN '2019-01-01 00:00:00' AND '2019-01-01 23:59:59'
OR (updated_date < '2019-02-01' AND id >= 1));

クエリビルダでの書き方

$test = Users::query()
   ->select('name')
   ->where(function ($query) {
                // created_dateが2019-01-01中
                $query->whereBetween('created_date', ['2019-01-01 00:00:00', '2019-01-01 23:59:59'])
                    // またはupdated_dateが2019-02-01より前、かつidが1以上
                    ->orWhere(function ($query) {
                        $query->where('updated_date', '<', '2019-02-01')
                            ->where('id', '>=', 1);
                    });
            })
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

レンタルサーバーで無理くりLaravel + Nuxt + PWAをやってみた

PWA Advent Calendar 2019 4日目の記事です。
PWAチックなのはNuxt.jsのNuxt PWAを使っているところのみです。
レンタルサーバーで無理くりLaravel + Nuxt + PWAをやってみました。

試してみたものの概要

  • レンタルサーバー (XREA)
  • Laravel 6.x
  • Nuxt.js 2.10 (Nuxt PWA)

結論

そもそもレンタルサーバーでLaravelを動かすべきではないし、
Nuxt.jsとLaravelを同一サーバーで管理するのが面倒臭すぎました。

前提条件

  • レンタルサーバーでSSLを設定済み
  • Laravel 6.xをローカル環境で構築済み
  • ローカル環境のNode.jsをインストール済

事前準備

npx @vue/cliをインストールする

$ npm i -g npx @vue/cli

LaravelのプロジェクトディレクトリにNuxtを入れる

$ npx create-nuxt-app nuxt

選択肢は適当に。

Nuxtのインストールが完了したら、Nuxtの設定関連ファイル群をLaravelのルートディレクトリに移動させる。

$ cd nuxt
$ mv .babelrc jest.config.js nuxt.config.js package-lock.json package.json ../

Nuxtに必要なモジュール(nuxt-laravel)を入れ直す

$ cd -
$ npm i --D nuxt-laravel@next @nuxtjs/axios @nuxtjs/proxy @nuxtjs/pwa

nuxt.config.jsを編集

nuxt.config.js
module.exports = {
  srcDir: 'nuxt', //追加
  mode: 'spa', //追加
  modules: [
    'nuxt-laravel'
    '@nuxtjs/pwa', //追加
  ],
  router: {
    base: '/app/' //追加 任意のディレクトリ名
  },
}

package.jsonを編集

Laravelベースの設定になっているため、scriptsの箇所をごっそり編集する

package.json



  "scripts": {
    "dev": "nuxt",
    "build": "nuxt build",
    "start": "nuxt start",
    "generate": "nuxt generate",
    "test": "jest",
    "postinstall": "npm run build"
  },

pallares/laravel-nuxtをComposerからインストール

$ composer require pallares/laravel-nuxt

Nuxtの開発環境サーバーを動かす

npm run dev実行後、localhost:3000/app/にアクセス

$ npm run dev

Nuxtのデフォルトが見えればおk。

Laravel Nuxt用のトップページを作成する

とりあえず動かすだけなら./nuxt/pages/index.vueのコピーでOK。

nuxt
├── pages
│   ├── README.md
│   ├──  top.vue #新規作成
│   └──  index.vue

Laravelのルーティングの設定

/app/topのURLでNuxtのtop.vueを表示するように設定する。

routes
├── api.php
├── channels.php
├── console.php
└── web.php #編集
    Route::get('/', function () {
        return redirect('/app/top');
    });

    Route::get('/app', function () {
        return redirect('/app/top');
    });

    Route::get(
      '/app/top',
      '\\'.Pallares\LaravelNuxt\Controllers\NuxtController::class
    );

本番環境のコンパイル

$ npm run build

でNuxt.jsのビルドを実行する

ローカルでLaravel Nuxt用のトップページを確認する

PHPのBuiltinサーバーでなら

$ php artisan serve

を実行して、localhost:8000/app/topにアクセスして、Laravel Nuxt用のトップページ(Nuxtのデフォルト画面)を確認する。

レンタルサーバー用の.htaccessを作成

.htaccessプロジェクトのルートディレクトリに置いて、publicデフォルトを無理やりドキュメントルートに変更します。
そうしないとLaravelがレンタルサーバーで基本的には動作しません。
**ドキュメントルートを直接弄れるレンタルサーバーであれば別です**

<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews
    </IfModule>

    RewriteEngine On

    RewriteCond %{REQUEST_FILENAME} -d [OR]
    RewriteCond %{REQUEST_FILENAME} -f
    RewriteRule ^ ^$1 [N]

    RewriteCond %{REQUEST_URI} (\.\w+$) [NC] #追加
    RewriteRule ^(.*)$ public/$1 #追加

    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ server.php

    RewriteRule ^(.*)/+$ $1 [R=301,L]
</IfModule>

レンタルサーバーにファイルアップロード

レンタルサーバーはたぶんFTPかSFTPですので、FileZillaなどを使用して、レンタルサーバーの公開ディレクトリにnode_modulesを除く全ファイルをアップロードします。メチャクチャ時間かかります。

.envvendorも必ずアップロードしてください。
でないと動きません。

アップロードしたレンタルサーバーのURLにアクセス

「https://${domain}/app/top」にアクセスしてください。

スクリーンショット 2019-12-04 09.57.34.png

インストールのボタンが出ており、PWAとしてインストールが可能なはずです。

最後に

レンタルサーバーで無理くりLaravel + Nuxt + PWAを試してみて、一旦は動くものの、まず実運用には耐えられないと思います。
Laravelのマイグレーションは基本SQLを発行して、SQLをphpmyadminか何かでimportし、デプロイはvendorから何から何までアップロードするため、アップロードミスや漏れも発生しやすいです。

もし実運用を目指すのであれば、Laravelは使わずにNuxtで静的に生成したファイル群をレンタルサーバーで設置するのであれば、お手軽にNuxt + PWAを試せると思います。

追記

明日はセルフバトン渡します。
何も考えずに始める意識低い系のPWAの予定です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel の "認可" まとめ(Gate, Policy 等)

はじめに

この記事は Laravel Advent Calendar 2019 - Qiita の 4日目 の記事です?

「この認可処理はどこに書くべきか」みたいなディスカッションから
Laravel の認可機能を深掘りする機会があったので、
改めて整理してみたいと思います?

認証については、よろしければこちらも!
Laravel の Guard(認証) って実際何をやっているのじゃ?

そもそも "認可" とはなんなのか

著者に対してだけブログ記事の更新を許可する
みたいなやつが "認可" です。

"認可" を真面目に考察したら、それだけで一記事書けてしまう、深い深いテーマです:hole:

"認可" についてのざっくりとした理解

いったんここでは、以下の理解で記事を進めようと思います。
(異論あればコメントにお願いします!)

  • 特定の条件に対して、リソースに対するアクションの権限を与える
    (e.g. 「記事の著者」という条件に対して、記事を更新する権限を与える)
  • 多くの場合、認証に基づく
    (著者としてログイン済の場合に↑の認可が成り立つ)

クラスメソッドさんの以下の記事がていねいな解説でわかりやすいので、
深掘りしたい方はぜひご参考ください!
よくわかる認証と認可

Laravel で "認可" する方法

ここからが本題?
Laravel では認可のために以下の仕組みが用意されています。

  • Gate
  • Policy
  • Request -> authorize()

それぞれ詳しく見ていきます?

Gate

Gates are Closures that determine if a user is authorized to perform a given action and are typically defined in the App\Providers\AuthServiceProvider class using the Gate facade.
https://laravel.com/docs/6.x/authorization#gates

ゲートは、特定のアクションを実行できる許可が、あるユーザーにあるかを決めるクロージャのことです。
https://readouble.com/laravel/6.x/ja/authorization.html#%E3%82%B2%E3%83%BC%E3%83%88

認可について「リソースに対するアクションの権限を与える」と上述しましたが、
なんといきなりこの「リソース」が登場しないのが Gate です?

  • クロージャーでシンプルに実装できる
  • モデルやリソースに紐づかないアクションも認可できる

というのが Gate の特徴です。

Gate で認可するには、 AuthServiceProviderboot メソッド内に Gate ファサードを使って定義します。

AuthServiceProvider.php
Gate::define('edit-settings', function ($user) {
    return $user->isAdmin;
});

ドキュメントには

Gates are most applicable to actions which are not related to any model or resource, such as viewing an administrator dashboard.
管理者のダッシュボードのように、モデルやリソースとは関連しないアクションに対し、ゲートは主に適用されます。

と書かれていますが、リソースに対する認可が「できない」わけではありません。
サンプルにある通り以下のような実装も可能です。

AuthServiceProvider.php
Gate::define('update-post', function ($user, $post) {
    return $user->id === $post->user_id;
});

ただしなんでもかんでも Gate に実装すると破綻するので、原則

  • モデルやリソースに関するものは Policy
  • それ以外は Gate

のように使い分けるのが良さそうです。

Policy

policies, like controllers, group their logic around a particular model or resource.
//
Once the policy has been registered, you may add methods for each action it authorizes.
https://laravel.com/docs/6.x/authorization#writing-policies

ポリシーとは、特定のモデルやリソースに対するロジックをまとめたものです。
//
ポリシーは特定のモデルやリソースに関する認可ロジックを系統立てるクラスです。
https://readouble.com/laravel/6.x/ja/authorization.html#%E3%82%A4%E3%83%B3%E3%83%88%E3%83%AD%E3%83%80%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3

Policy については「モデルやリソースに対する」と明記されており、より "認可" らしい(?)機能です。

使い方の詳細はドキュメントに記述されているので省略しますが、
以下の手続きで使えます。

  • Policy クラスを作る
  • AuthServiceProvider で Policy クラスを登録する(省略可※後述

Policy クラスはモデルに応じて、クラス内のメソッドはアクションに応じて作ります。

以下ドキュメントより引用

PostPolicy.php
class PostPolicy
{
    public function update(User $user, Post $post)
    {
        return $user->id === $post->user_id;
    }
}

Policy は コントローラーや middleware で can cant を呼び出して利用します。

PostController.php
if ($user->can('update', $post)) {
    //
}
routes/web.php
Route::put('/post/{post}', function (Post $post) {
    // 現在のユーザーはこのポストを更新できる
})->middleware('can:update,post');

Request -> authorize()

実は Request クラスでも認可処理を書くことができます。
artisan で Request クラスを作ると以下のメソッドが作られるはず。
このメソッド内でユーザーアクションを認可できます。

    public function authorize() {
        return true;
    }

自分はこの authorize() を触ることはほぼないんだけど、どういうときに使うのかな??
「ポリシーを使いたいけど、 $user->can() を Controller に書きたくないし、 middleware として使うのにも適していない」
みたいなシーンがあれば、ここに書くと良いのかもしれない。
Request クラスの中に認可とバリデーションがまとまってると、後から見たときにわかりやすいかも?
機会があれば試してみよう?

Gate / Policy の便利機能たち

モデルポリシーの自動検出機能(Auto-Discovery Of Model Policies)

Laravel 5.8 で追加された機能です。
Policy を使うには モデルとポリシーのマッピングを AuthServiceProvider で登録する必要がありましたが、
標準命名規則に従っている場合はこの登録作業が不要になりました。

app/User.php というモデルがある場合は app/Policies 下の UserPolicy をPolicy として自動検出します。
しかしほとんどの場合、 app 直下にモデルを置くことはないでしょう。
app/Models 以下ににモデルを配置したときは app/Models/Policies に Policy を書けということになりますが、これは気持ち悪い・・・
こんなとき、 AuthServiceProviderboot() 内で以下のように定義しておけば、app/Policies 以下で自動検出してくれるようです?

AuthServiceProvider.php
Gate::guessPolicyNamesUsing(function ($modelClass) {
    return 'App\\Policies\\' . class_basename ($modelClass).'Policy';
});

認可レスポンスの改善(Improved Authorization Responses)

Laravel 6 で追加された機能です。
Gate や Policy で true / false を返す代わりに Response::allow() / Response::deny() を返し、
呼び出し側では Gate::inspect() を呼び出すことで、 Policy から認可レスポンスを受け取ることができるようになりました。
deny 時に Policy で詳細なメッセージを設定してそれを画面表示する、みたいなことが簡単にできるようになりましたね?

PostPolicy.php
public function update(User $user, Post $post)
{
    return $user->id === $post->user_id
                ? Response::allow()
                : Response::deny('You do not own this post.');
}
PostController.php
$response = Gate::inspect('update', $post);

if ($response->allowed()) {
    // アクションは認可された…
} else {
    echo $response->message();
}

ゲストユーザーゲート/ポリシー(Guest User Gates / Policies)

これまでゲートやポリシーでは、未認証のユーザーに対しては一律 false を返していましたが、
以下のようにタイプヒントで $user を nullable にすることでチェックをパスさせられるようになりました。

ドキュメントには以下の例が載っていて

AuthServiceProvider.php
Gate::define('update-post', function (?User $user, Post $post) {
    // ...
});

どう考えてもダメな使い方(未ログインなら記事を編集できちゃう)なので、「この機能いる??」と疑問が湧きます?
ユースケースとしては、以下の PR への Gary さんのコメントがわかりやすかったです。

$this->authorize('view', $promotion) -> Is it christmas time? You can view that promotion, even as a guest.
$this->authorize('view', $picture) -> Is the picture publicly viewable? You can view those pictures, even as a guest.
$this->authorize('view', $page) -> Is this page published? You can view the page, even as a guest.
$this->authorize('view', $dashboard) -> Is this guest from a known EU IP address? You can view the dashboard, even as guest.

最初に認可は「多くの場合、認証に基づく」と書きましたが、
日時・リソースの状態・IPなど、 認証に基づかない認可を実現したいときに、この機能を利用できます。

PR コメントで賛否のディスカッションが盛り上がっていて面白かったです。
[5.7] Allow Gates / Policies To Accept "Guests"

まとめ

やはり認可の基本は Gate / Policy ですね!
バージョンアップ毎に機能改善が行われているのでじゃんじゃん活用しましょう?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel6】日本語化

内容

laravelに追加した認証処理とかを日本語化したい

やったこと

1.設定ファイルのlangをjaに変更

2.コマンド実行
https://readouble.com/laravel/5.6/ja/validation-php.html
にある下記コマンドをコマンドプロンプト/VSCodeターミナルで実行するとディレクトリが作成される
(Windows環境ではまだ試していません。)とあるが問題なく可能

php -r "copy('https://readouble.com/laravel/5.6/ja/install-ja-lang-files.php', 'install-ja-lang.php');"
php -f install-ja-lang.php
php -r "unlink('install-ja-lang.php');"

3.jsonファイル追加

下記を追加して、それぞれの置き換え文言を追加していく

resources/lang/ja.json
{
    "Register": "参加登録",
    "Login": "ログイン",
    "Name": "名前",
    "E-Mail Address": "メールアドレス",
    "Password": "パスワード",
    "Confirm Password": "パスワード再入力",
    "Remember Me": "ログイン情報を記録",
    "Forgot Your Password?": "パスワード忘れの場合",
    "Reset Password": "パスワードリセット",
    "Send Password Reset Link": "パスワードリセットメールを送信",
    "Verify Your Email Address": "メールアドレスを認証してください",
    "A fresh verification link has been sent to your email address.": "新規の認証リンクを再度送信しました",
    "Before proceeding, please check your email for a verification link.": "送信メール内の認証リンクをから認証を行ってください。",
    "If you did not receive the email,": "メールが届いていない場合、",
    "click here to request another.": "こちらクリックで再度メール送信します。"
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelでページャーを常時表示する

やりたいこと

Laravelのデフォルトのペジネータは2ページ目がないと非表示になってしまう。
これを常時表示したい。

やりかた

php artisan vendor:publish --tag=laravel-paginationを実行し、ペジネータのテンプレートを出力する。
/resources/views/vendor/pagination/bootstrap-4.blade.phpに出力されたファイルを編集する。

bootstrap-4.blade.php
@if ($paginator->hasPages())
    <nav>
        <ul class="pagination">
            {{-- Previous Page Link --}}
            @if ($paginator->onFirstPage())
                <li class="page-item disabled" aria-disabled="true" aria-label="@lang('pagination.previous')">
                    <span class="page-link" aria-hidden="true">&lsaquo;</span>
                </li>
            @else
                <li class="page-item">
                    <a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev" aria-label="@lang('pagination.previous')">&lsaquo;</a>
                </li>
            @endif
            {{-- Pagination Elements --}}
            @foreach ($elements as $element)
                {{-- "Three Dots" Separator --}}
                @if (is_string($element))
                    <li class="page-item disabled" aria-disabled="true"><span class="page-link">{{ $element }}</span></li>
                @endif
                {{-- Array Of Links --}}
                @if (is_array($element))
                    @foreach ($element as $page => $url)
                        @if ($page == $paginator->currentPage())
                            <li class="page-item active" aria-current="page"><span class="page-link">{{ $page }}</span></li>
                        @else
                            <li class="page-item"><a class="page-link" href="{{ $url }}">{{ $page }}</a></li>
                        @endif
                    @endforeach
                @endif
            @endforeach
            {{-- Next Page Link --}}
            @if ($paginator->hasMorePages())
                <li class="page-item">
                    <a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next" aria-label="@lang('pagination.next')">&rsaquo;</a>
                </li>
            @else
                <li class="page-item disabled" aria-disabled="true" aria-label="@lang('pagination.next')">
                    <span class="page-link" aria-hidden="true">&rsaquo;</span>
                </li>
            @endif
        </ul>
    </nav>
@endif

最初の行のif文が、$paginator->hasPages()の条件でページがなければページャーを出力しないようになっているため、このif文をとってやれば常時出力されるようになる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む