20200224のlaravelに関する記事は10件です。

クラス内のメソッド情報を抜き出すためには

前書き

PHPにはクラス情報を取得するReflectionClassという関数があります。
この関数を使うとメソッドなどの行番も取得できるため、この行番情報とfile関数を併用することでコード内のメソッド一覧を取得できるのではと考えてみました。

サンプルコード

use Composer\Autoload\ClassMapGenerator;
use LogicException;
use ReflectionClass;

/**
 * Class ReflectionClassCode
 */
class ReflectionClassCode
{
    /**
     * クラス内のメソッドのコードを取得する
     *
     * @param array $class_paths
     * @param int|null $filter
     * @return array
     */
    public function getMethodCode(array $class_paths, int $filter = null)
    {
        $classes = [];
        foreach ($class_paths as $class_path) {
            foreach (ClassMapGenerator::createMap($class_path) as $model => $class_file_path) {
                $code_texts = file($class_file_path);
                $code_texts = collect($code_texts)->prepend('')->all();

                $method_codes = [];
                $reflection = new ReflectionClass($model);

                // 継承クラスのメソッドは除外したものを取得する
                $methods = collect($reflection->getMethods($filter))->where('class', $model)->all();
                foreach ($methods as $method) {
                    if ($method->getName() === '__construct') {
                        continue;
                    }

                    if (empty($method->getDocComment())) {
                        throw new LogicException('メソッドのPhoDocが未定義です。必ず入力して下さい');
                    }

                    $code_length = $method->getEndLine() - $method->getStartLine() + 1;
                    $method_code_doc_comments = $method->getDocComment() . PHP_EOL;
                    $method_code = array_slice($code_texts, $method->getStartLine(), $code_length);
                    $method_codes[$method->getName()] = '    ' . $method_code_doc_comments . collect($method_code)->join('');
                }

                $classes[$class_file_path] = $method_codes;
            }
        }

        return $classes;
    }

    /**
     * クラス内のメソッドのコードを取得する
     *
     * @param string $class_path
     * @param int|null $filter
     * @return array
     */
    public function findMethodCode(string $class_path, int $filter = null)
    {
        return collect($this->getMethodCode([$class_path], $filter))->first();
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

一晩でReact×Laravel(API)×Dockerを利用したWebサービスを作ったTDD(徹夜駆動開発)のお話

はじめに

初めまして、大阪でWebエンジニアをしているかっぽと申します。
今回、主催者の並里氏のお誘いもあり大阪で行われた一泊二日のエンジニア合宿に参加してきたのですが
思っていた以上に刺激的で有意義な時間を過ごせましたので記事にしようと思い書いている次第です。
こちらの記事は合宿についての自分チーム主体の偏見の塊のような個人的共有レポートです。

第1回大阪エンジニア開発合宿(OsakaDevelopmentCamp)とは

2020/02/22(土) 〜 2020/02/23(日) に大阪の難波周辺で行われたエンジニアの開発合宿
Laravelのもくもく会や勉強会、ハンズオンなど多くのエンジニアのイベントを企画しているナミザト氏の今度開発合宿やってみたいな〜というぽっとでのツイートをきっかけに賛同したフットワーク軽めのエンジニア9人が集まり、3人1チームとなって前日から次の日の朝まで眠らずにひとつWebサービスを開発するというエンジニアにとって夢のような合宿です

前置き

今回の開発合宿の参加者

チームサイトウ

チームアマゾネス

チームじゃけぇ

合宿日時

2020/02/22(土) 16:00 〜 2020/02/23(日) 10:00

当日の予定

当日はチームで一つの成果物(web/アプリ問わない)を23日の7時までに作成して、
23日の8時にはみんなの前でそれぞれ成果物を発表する

合宿のルール

  • 技術選定はチームで話し合い決定する
  • 成果物はチームで話し合い決定する
  • 開発終了後の成果物の所有等はチームで話し合い決定する(自由)

今回の合宿、開始日までに環境構築や事前準備はOKですが、実装は開始日の16:00からしか行ってはいけないという暗黙のルールがありました。
しかし、なんと3チーム中2チームが16:00以前から実装を進めていたとの情報が!!(ずるい)

16:30~ 合宿開始

そんなこんなで始まった開発合宿ですが、さすが有名なエンジニアさんが多く集まっているだけあって、始まったとたんからバリバリと実装を開始しました。(私のチーム以外は。)
IMG_0706.JPG

ふんわりとしか役割を決めていなかった私のチーム、チームじゃけぇはその姿を見て呆然とし、

「え、あ、フロントする?サーバする?」
「...どっちやりたい?」
「どっちでも...」

とスタートから挙動不審
幸先の悪いスタートとなりました。

今回チームじゃけぇが開発合宿で開発した成果物と選定した技術スタック

今回チームじゃけぇが開発合宿で開発したのは...

React×Laravel(API)×Dockerを利用したある分野に特化した専用掲示板!!

※正式な公表のタイミングがあると思うので名前はあえて控えます

フロントエンド: React (じゃけぇ担当)
サーバサイド: Laravel API (はる・かっぽ担当)

今回のソースコードのフォルダ構成ですが、ReactとLaravelを同じ階層のリポジトリに置き、開発環境はDockerを利用、本番環境のデプロイ先はフロント・サーバそれぞれのサーバという構成でWebサービスを作りました。(この構成、個人的にすごくやりやすかったです)
余裕があればGitHub共有したいと思います!

Dockerについて

フロントのDockerはcreate-react-appを参考に
サーバのDockerはphp-for-everyoneを参考に
フロント・サーバの各々のDockerの良さを引っ張ってきて、つなぎこんでガッチャンコした
今回特別に作ったDockerを使用しました。
GitHubについては、落ち着いたらリポジトリとは別にReact×Laravel(API)×Dockerの環境構築用としてDocker環境を共有する予定です。
Facebookの皆さん、Laravel-shibuyaの皆さん 参考にさせていただきました。ありがとうございます。)

本番サーバについて

ReactはNow
LaravelはHerku
のサーバを利用しました。
Laravelは完全にAPIとし、DBの値やビジネスロジックでゴニョゴニョした値をJSON型で返す処理のみで、データ表示はReactにすべてお任せました。
デプロイは、masterブランチにmergeされたと同時に自動で本番反映されるように設定致しました。

17:00~ チームじゃけぇ始動

それぞれの役割も決まり、チームじゃけぇもいざ開発スタートです!

当日までに、はるさんがWebサービスのデザインをFigmaで作成していただいていたのと、じゃけぇさんの技術力でフロントは初速充分でバリバリ進みます。

肝心のサーバサイドはというと...

私自身、普段PHPのSymfonyを使用しており、Laravelの業務経験はありましたが約一年ぶりに触ります。
はるさんも普段はJavaを使った開発が多いとのことで

「ヤベェ、どうします?w」

「まずはAPIの設定から?」

とあたふた。

しかし、人間の記憶とは素晴らしいもので、一度触り始めると1年前のLaravelの記憶がどんどん蘇ってきました。
まさに、「書けるっ!書けるぞっ!!」状態

はるさんも吸収力がナプキンレベル、1つ進めると後は自走状態に。

そこからは3人とも、時間を忘れるように開発に没頭しました。

20:00~ 晩御飯

今回の合宿の晩御飯は豪華!
宅配のピザLsize3枚(チーズ・照り焼き・マヨポテト)
寿司を5人前(サーモンセット3人前・マグロセット2人前)
お寿司の注文にはUberEatsを利用しました。

20:00過ぎに全ての注文が届き、支払いも済ませたので食事にしましょうと皆さん一休み。
いざ食べようと広げてみると...

「あれ?マグロセット2人前なくね?」

どういうことか、支払いはお寿司5人前分したのに注文したマグロセット2人前が配達されませんでした。
なにしとんねんUberEats
IMG_0702.JPG

21:00~ 再び事件

ご飯も食べ終えたので開発する人部屋に残り、銭湯行く人は行きましょうということになりました。
私とはるさんは再び開発を進め、じゃけぇさんは銭湯に。

ここでまたまた事件発生

なんと、突然フロントが表示されない!!!

しかも、フロント担当のじゃけぇさんが銭湯に行ってしまっていません。

ドヤ顔で
「yarn install したら治るでしょ」 と高を括っていました。

が、インストールしても

「あれ?治らない、、、」

結局、はるさんと1時間ほど格闘しました。

じゃけぇさんが帰ってきて解決しましたが、
結論、yarn install の場所を間違えていました。

しょうもないことで時間取られてしまう エンジニアあるあるですよね。

これにより、フロント頼りの開発を辞め、API開発時に便利なGoogleChromeの拡張ツールTalend Api Tester
を使いこなせるようになりました。(いや、使ってなかったんかい!)

22:00~6:00 空白の8時間

ふと時計を見るともう6時。

ほとんど記憶がございません。
意識朦朧の中、必死で実装しまくってました。

結果的には、
フロント側は基本的なフォーム作成やデザイン実装から、ストレージを利用したデータ保存機能まで。
サーバ側はAPIでのRouting・基本的なCRUD処理からLaravelのEloquentを利用したソート切り替え処理まで。

難なくクリアし、どちらともキリの良いところで実装を終えられました。万歳。

8:00~ 成果物発表

やはり、一晩で3人で1つのWebサービスを作り上げるのは至難の業。
結論を言うと、3チームとも時間の問題もあり、完成に至らずでした。
しかし、全員が触ったことのない新しい技術に挑戦するといった目標を掲げ最後まで諦めずに頑張るチーム
Laravelの認証周りの理解に苦しみながらも様々な記事から豊富な知識を蓄えたチーム等、
どのチームも開発に没頭し、誰一人脱落することなく開発合宿を終えることができました!

成果物発表が終わったあとは眠さの限界でチェックアウトの時間まで床で爆睡。
私一人だけ、昨日の残りのピザの残飯処理をすると言うカオスな光景となりました。
IMG_0704.JPG

まとめ

以上、今回の大阪エンジニア開発合宿(OsakaDevelopmentCamp)の個人的レポートでした。
個人的には、様々なトラブルを乗り越えぶっ通しで久々にLaravel触れることができて大変嬉しかったです!
貴重な体験をさせていただきまして、主催者の並里氏をはじめ参加された皆様、ありがとうございました!
そしてお疲れ様でした!
次回開催はいつになるかわかりませんが大いに楽しみですね?
IMG_0709.JPG
(疲れ切った表情の皆さんを載せるのは心が痛かったのでマンウィズっぽく)

有志ですがプロダクト参加者募集してます

今回実装した掲示板Webサービスですが React×Laravel(API)×Docker で環境構築されており、プロダクト自体も初学者に大変わかりやすく勉強になるプロダクトだと自負しております。
3人とも完全に趣味の範囲でやっておりますので、お給料等をお支払いすることはできませんが、もしこのプロダクトに参加して一緒にLaravel・Reactの勉強をしたり自分のポートフォリオの一つとして利用したいというエンジニアの方(実務未経験でも可)いらっしゃいましたら気軽にDM・リプでご連絡くださいませ〜
一緒に楽しくWebサービスを作りましょう?

最後までお読みいただきありがとうございました〜?

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

開発合宿でReact×Laravel(API)×Dockerを利用したWebサービスを作ったお話

はじめに

初めまして、大阪でWebエンジニアをしているかっぽと申します。
今回、主催者の並里氏のお誘いもあり大阪で行われた一泊二日のエンジニア合宿に参加してきたのですが
思っていた以上に刺激的で有意義な時間を過ごせましたので記事にしようと思い書いている次第です。
こちらの記事は合宿についての自分チーム主体の偏見の塊のような個人的共有レポートです。

第1回大阪エンジニア開発合宿(OsakaDevelopmentCamp)とは

2020/02/22(土) 〜 2020/02/23(日) に大阪の難波周辺で行われたエンジニアの開発合宿
Laravelのもくもく会や勉強会、ハンズオンなど多くのエンジニアのイベントを企画しているナミザト氏の今度開発合宿やってみたいな〜というぽっとでのツイートをきっかけに賛同したフットワーク軽めのエンジニア9人が集まり、3人1チームとなって前日から次の日の朝まで眠らずにひとつWebサービスを開発するというエンジニアにとって夢のような合宿です

前置き

今回の開発合宿の参加者

チームサイトウ

チームアマゾネス

チームじゃけぇ

合宿日時

2020/02/22(土) 16:00 〜 2020/02/23(日) 10:00

当日の予定

当日はチームで一つの成果物(web/アプリ問わない)を23日の7時までに作成して、
23日の8時にはみんなの前でそれぞれ成果物を発表する

合宿のルール

  • 技術選定はチームで話し合い決定する
  • 成果物はチームで話し合い決定する
  • 開発終了後の成果物の所有等はチームで話し合い決定する(自由)

今回の合宿、開始日までに環境構築や事前準備はOKですが、実装は開始日の16:00からしか行ってはいけないという暗黙のルールがありました。
しかし、なんと3チーム中2チームが16:00以前から実装を進めていたとの情報が!!(ずるい)

16:30~ 合宿開始

そんなこんなで始まった開発合宿ですが、さすが有名なエンジニアさんが多く集まっているだけあって、始まったとたんからバリバリと実装を開始しました。(私のチーム以外は。)
IMG_0706.JPG

ふんわりとしか役割を決めていなかった私のチーム、チームじゃけぇはその姿を見て呆然とし、

「え、あ、フロントする?サーバする?」
「...どっちやりたい?」
「どっちでも...」

とスタートから挙動不審
幸先の悪いスタートとなりました。

今回チームじゃけぇが開発合宿で開発した成果物と選定した技術スタック

今回チームじゃけぇが開発合宿で開発したのは...

React×Laravel(API)×Dockerを利用したある分野に特化した専用掲示板!!

※正式な公表のタイミングがあると思うので名前はあえて控えます

フロントエンド: React (じゃけぇ担当)
サーバサイド: Laravel API (はる・かっぽ担当)

今回のソースコードのフォルダ構成ですが、ReactとLaravelを同じ階層のリポジトリに置き、開発環境はDockerを利用、本番環境のデプロイ先はフロント・サーバそれぞれのサーバという構成でWebサービスを作りました。(この構成、個人的にすごくやりやすかったです)
余裕があればGitHub共有したいと思います!

Dockerについて

フロントのDockerはcreate-react-appを参考に
サーバのDockerはphp-for-everyoneを参考に
フロント・サーバの各々のDockerの良さを引っ張ってきて、つなぎこんでガッチャンコした
今回特別に作ったDockerを使用しました。
GitHubについては、落ち着いたらリポジトリとは別にReact×Laravel(API)×Dockerの環境構築用としてDocker環境を共有する予定です。
Facebookの皆さん、Laravel-shibuyaの皆さん 参考にさせていただきました。ありがとうございます。)

本番サーバについて

ReactはNow
LaravelはHerku
のサーバを利用しました。
Laravelは完全にAPIとし、DBの値やビジネスロジックでゴニョゴニョした値をJSON型で返す処理のみで、データ表示はReactにすべてお任せました。
デプロイは、masterブランチにmergeされたと同時に自動で本番反映されるように設定致しました。

17:00~ チームじゃけぇ始動

それぞれの役割も決まり、チームじゃけぇもいざ開発スタートです!

当日までに、はるさんがWebサービスのデザインをFigmaで作成していただいていたのと、じゃけぇさんの技術力でフロントは初速充分でバリバリ進みます。

肝心のサーバサイドはというと...

私自身、普段PHPのSymfonyを使用しており、Laravelの業務経験はありましたが約一年ぶりに触ります。
はるさんも普段はJavaを使った開発が多いとのことで

「ヤベェ、どうします?w」

「まずはAPIの設定から?」

とあたふた。

しかし、人間の記憶とは素晴らしいもので、一度触り始めると1年前のLaravelの記憶がどんどん蘇ってきました。
まさに、「書けるっ!書けるぞっ!!」状態

はるさんも吸収力がナプキンレベル、1つ進めると後は自走状態に。

そこからは3人とも、時間を忘れるように開発に没頭しました。

20:00~ 晩御飯

今回の合宿の晩御飯は豪華!
宅配のピザLsize3枚(チーズ・照り焼き・マヨポテト)
寿司を5人前(サーモンセット3人前・マグロセット2人前)
お寿司の注文にはUberEatsを利用しました。

20:00過ぎに全ての注文が届き、支払いも済ませたので食事にしましょうと皆さん一休み。
いざ食べようと広げてみると...

「あれ?マグロセット2人前なくね?」

どういうことか、支払いはお寿司5人前分したのに注文したマグロセット2人前が配達されませんでした。
なにしとんねんUberEats
IMG_0702.JPG

21:00~ 再び事件

ご飯も食べ終えたので開発する人部屋に残り、銭湯行く人は行きましょうということになりました。
私とはるさんは再び開発を進め、じゃけぇさんは銭湯に。

ここでまたまた事件発生

なんと、突然フロントが表示されない!!!

しかも、フロント担当のじゃけぇさんが銭湯に行ってしまっていません。

ドヤ顔で
「yarn install したら治るでしょ」 と高を括っていました。

が、インストールしても

「あれ?治らない、、、」

結局、はるさんと1時間ほど格闘しました。

じゃけぇさんが帰ってきて解決しましたが、
結論、yarn install の場所を間違えていました。

しょうもないことで時間取られてしまう エンジニアあるあるですよね。

これにより、フロント頼りの開発を辞め、API開発時に便利なGoogleChromeの拡張ツールTalend Api Tester
を使いこなせるようになりました。(いや、使ってなかったんかい!)

22:00~6:00 空白の8時間

ふと時計を見るともう6時。

ほとんど記憶がございません。
意識朦朧の中、必死で実装しまくってました。

結果的には、
フロント側は基本的なフォーム作成やデザイン実装から、ストレージを利用したデータ保存機能まで。
サーバ側はAPIでのRouting・基本的なCRUD処理からLaravelのEloquentを利用したソート切り替え処理まで。

難なくクリアし、どちらともキリの良いところで実装を終えられました。万歳。

8:00~ 成果物発表

やはり、一晩で3人で1つのWebサービスを作り上げるのは至難の業。
結論を言うと、3チームとも時間の問題もあり、完成に至らずでした。
しかし、全員が触ったことのない新しい技術に挑戦するといった目標を掲げ最後まで諦めずに頑張るチーム
Laravelの認証周りの理解に苦しみながらも様々な記事から豊富な知識を蓄えたチーム等、
どのチームも開発に没頭し、誰一人脱落することなく開発合宿を終えることができました!

成果物発表が終わったあとは眠さの限界でチェックアウトの時間まで床で爆睡。
私一人だけ、昨日の残りのピザの残飯処理をすると言うカオスな光景となりました。
IMG_0704.JPG

まとめ

以上、今回の大阪エンジニア開発合宿(OsakaDevelopmentCamp)の個人的レポートでした。
個人的には、様々なトラブルを乗り越えぶっ通しで久々にLaravel触れることができて大変嬉しかったです!
貴重な体験をさせていただきまして、主催者の並里氏をはじめ参加された皆様、ありがとうございました!
そしてお疲れ様でした!
次回開催はいつになるかわかりませんが大いに楽しみですね?
IMG_0709.JPG
(疲れ切った表情の皆さんを載せるのは心が痛かったのでマンウィズっぽく)

有志ですがプロダクト参加者募集してます

今回実装した掲示板Webサービスですが React×Laravel(API)×Docker で環境構築されており、プロダクト自体も初学者に大変わかりやすく勉強になるプロダクトだと自負しております。
3人とも完全に趣味の範囲でやっておりますので、お給料等をお支払いすることはできませんが、もしこのプロダクトに参加して一緒にLaravel・Reactの勉強をしたり自分のポートフォリオの一つとして利用したいというエンジニアの方(実務未経験でも可)いらっしゃいましたら気軽にDM・リプでご連絡くださいませ〜
一緒に楽しくWebサービスを作りましょう?

最後までお読みいただきありがとうございました〜?

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

ファイルアップローダーサービス ensha.red をリリースした話 〜バックエンド篇 Part 1 〜

はじめに

enshared というファイルアップローダーサービスを 2 月半ばに魂と生命を生贄にリリースしました。
note にもリリースまでの過程を軽く書いていますが、技術的なことはほぼ書いてないので、技術的なことを Qiita に書きます。

ファイルアップローダーサービスとは?

某巨大掲示板全盛期によく使われていました。「どっとうpろだ」とか有名だと思います。
ファイルをアップロードし、誰かに共有をする目的で使用するものです。

ただ、今までのファイルアップローダーは手軽に使える反面、アップロードをするファイルの細かい設定などは特に行えない印象がつよく、それを解決しようとして生まれたのが ensha.red です。

バックエンド篇

フロントエンド篇はまた別途書きます。

技術スタック

バックエンドの技術スタックは下記です。

  • Nginx
  • PHP 7.3 (Docker)
  • Laravel 5.8
  • Apache + mod_pagespeed (Reverse Proxy)

ファイルアップローダーを作るときに気をつけるべき点

ファイルアップローダーはユーザー入力に危険がはらむ可能性があるウェブアプリケーションです。
特に実行ファイルや動画、音楽ファイルのメタデータ、 JPEG の exif, PNG の iTxt などなどファイルの特性に対してメタデータに対するアプローチが異なります。

enshared は一部ファイルのメタデータをデフォルトで消すようになっていたり、エンコードし直したりして、ファイルサイズの縮小を行う設定があります(メタデータ削除含めこれは無効にすることもできます。)

JPEG の exif や PNG の iTxt の削除は ImageMagick を用いて行います。
ImageMagick はデフォルトでバンドルされていないことが多いので Dockerfile に下記のように書いて有効にしています

RUN apt-get install -y imagemagick libmagickwand-dev
RUN pecl install imagick
RUN docker-php-ext-enable imagick

exif や iTxt を消すには ImageMagick の stripImage を用います。

public function removeMetadata(): self
{
    $this->imagick->stripImage();
    return $this;
}

また、 ImageMagick のメモリ使用量を抑制するため、下記のように制限をかけます。

public function __construct($path)
{
    $this->imagick = new \Imagick($path);
    $this->imagick->setResourceLimit(
        \Imagick::RESOURCETYPE_MEMORY,
        100
    );
    $this->imagick->setResourceLimit(
        \Imagick::RESOURCETYPE_FILE,
        100
    );
}

GD を使わない理由は単純に画像ファイルに対して行える処理が少ない、というのがあります。
あと GD の場合純粋にエラーハンドリングが面倒くさいです。
本来であればバイナリファイルを読むというとても楽しいという行為を、サービスの公開という次元の話に落とし込むために今回は ImageMagick を使用しています。そもそも GD なんて必要なくバイナリファイルを読めばいい云々…の話をし出したら止まらなくなるので、再度いいますが、ImageMagick を使用しています。

enshared には ZIP ファイルの中身を覗ける機能があり ZIP ファイルを展開する際には ZIP BOMB などにも気をつけなければいけません。

あまり PHP での文献がなく、どうしようかなと考えた結果で実際にどう対策を行っているかというと、

// Check ZIP bomb
$zipArchive = new \ZipArchive();
$zipArchive->open($value->getPathname());

$i = 0;
while ($stat = $zipArchive->statIndex($i++)) {
    // ファイルサイズを見る
    if ($stat['size'] > static::MAX_FILE_SIZE
        || $stat['comp_size'] > static::MAX_FILE_SIZE
    ) {
        return false;
    }

    // 相対パスが含まれている場合はダメ
    if (preg_match('/(?:\/|)(?:\.\/|\.\.\/)/', $stat['name'])) {
        return false;
    }
}

さらに PHP で気をつけるべきは $_FILES の特性を知っておく必要がある、という点です。
$_FILES の MIME Type はクライアントから送信された値であり、実態がそうであるかというとそうではない可能性があります。

したがって、ファイルごとに何かしら処理を行うのであればファイルそのものの MIME Type を自分で調べる必要があります。
幸い、 Laravel には File::getMimeType() があるので、これを利用します。

そして PHP で作る際にもっとも気をつけるべき点はメモリへの展開です。
今まで見てきたプロジェクトのアップロード関連でよくしている誤りは下記です

ini_set('memory_limit', '-1');
// ...
file_get_contents($_FILES['tmp_name']);
if ((contents = php_stream_copy_to_mem(stream, maxlen, 0)) != NULL) {
    RETVAL_STR(contents);
} else {
    RETVAL_EMPTY_STRING();
}

PHP の file_get_contents は読み込んだデータをメモリ上に展開します (569 行目付近)
maxlen はデフォルトで -1 が指定されています。

これらの処理は PHP のソースコード上の _php_stream_copy_to_mem に該当します。

if (php_stream_stat(src, &ssbuf) == 0 && ssbuf.sb.st_size > 0) {
  max_len = ssbuf.sb.st_size + step;
} else {
  max_len = step;
}

result = zend_string_alloc(max_len, persistent);
ptr = ZSTR_VAL(result);

// TODO: Propagate error?
while ((ret = php_stream_read(src, ptr, max_len - len)) > 0){
  len += ret;
  if (len + min_room >= max_len) {
    result = zend_string_extend(result, max_len + step, persistent);
    max_len += step;
    ptr = ZSTR_VAL(result) + len;
  } else {
    ptr += ret;
  }
}
if (len) {
  result = zend_string_truncate(result, len, persistent);
  ZSTR_VAL(result)[len] = '\0';
} else {
  zend_string_free(result);
  result = NULL;
}

書くと長くなるので抑えておきますが、ここらへんでメモリ全てにぶっこんでいることがわかります。 (zend_string_extend で予め alloc しているメモリが足りなければ realloc している)

何が言いたいかというと、 file_get_contents はファイルの中身をすべて読み込み、メモリ上に展開をするわけです。なんなら余分に alloc もします。

ということでアップロードされたファイルのサイズが 1G や 2G などを超えたり、同時にアップロードが発生してメモリが圧迫された場合、などなど考えると file_get_contents でファイルの中身を全て取得するのはバッドプラクティスであるということがわかるかと思います。

解決策は readfile を使うか、 stream_copy_to_stream を使って読み込んでいきます。

enshared の場合は後者です。これには理由があり readfile だとファイルのパスを指定しますが、基本的に取り扱っているのが resource 型であるから、です。

stream_copy_to_stream(
    $stream,
    fopen('php://output', 'wb')
);

とてもかんたんですね。

enshared の場合、自前のサーバーではなくファイルをクラウドストレージに置いています。そのため、ファイルのデータをクラウドストレージから取得する際にも気をつける必要があります。

特に Guzzle を使用している場合 (string) $client->getBody() を使いがちですが、これもファイルの容量によってはメモリの使用量が著しく増えます。
したがって、そうはならないように、ストリームをチャンクに分けて読んでいきます。

で、 Guzzle のストリームをそのまま使えればよかったんですが、提供されていないっぽく getBody()->read を使ってチャンクに分けつつ別のストリームに書き出していく手法を取りました。

本来は Laravel の Storage::readStream にそのまま Guzzle のストリーム返せばええやろ!の気構えでいたんですが、若干工夫をする必要がありました。

public function readStream($path)
{
    /**
     * @var Response $file
     */
    $file = ...;

    $this->temps[$path] = tmpfile();

    $body = $file->getBody();
    while (!$body->eof()) {
        $read = $body->read(static::CHUNKS);
        fwrite($this->temps[$path], $read);
    };

    rewind($this->temps[$path]);
    return $this->temps[$path];
}

ローカル変数である tmpfile をそのまま返してしまうと GC の実行と重ねて削除される恐れがあるため、削除されないために一旦クラスのプロパティに格納しています。

Part1 は以上です!続きは Part2 に書きます。

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

そろそろaws初めてみよか#6~Elastic Beanstalkによるlaravel~

はじめに

前回まででCodeStar、EC2、RDSによるlaravel環境が整ったわけですので
次はElastic Beanstalkにチャレンジしてみます。

Elastic Beanstalkの環境構築

まずはPHPのサンプル環境を作成してみようと思います。
そこからlaravel色に染めましょう(^^♪

Elastic Beanstalkのコンソールから[今すぐ始める]をクリック
01.png
アプリケーション名はlaravel、プラットフォームはphp、アプリケーションコードはサンプルを選択します。
02.png
数分すると環境が出来上がります。
03.png
ダッシュボードのURL欄のURLをクリックするとデプロイされたアプリケーションにアクセスできます。
04.png
PHP7.3.11らしいです。
05.png

ほぼクリックだけで実行環境ができました。

Laravelアプリケーションの作成

さてLaravelにするにはどうしたらよいかと言うと、Vendor配下以外のファイルをZIPに固めてアップロードするとデプロイされます。
せっかくなのでチュートリアルに沿った形で進めたいと思います。
Elastic Beanstalk への Laravel アプリケーションのデプロイ

まずは既存のEC2 Laravel環境(前回作成した環境ね)に新規にLaravelプロジェクトを作成します。

[ec2-user@ip-172-31-29-127 ~]$ composer create-project --prefer-dist laravel/laravel eb-laravel

環境設定ファイル.envをいじります。
変更したのはDB_HOST,DB_DATABASE,DB_USERNAME,DB_PASSWORDです。

[ec2-user@ip-172-31-29-127 eb-laravel]$ cat .env
:
DB_CONNECTION=mysql
DB_HOST=database.culgyynq9ap1.us-east-2.rds.amazonaws.com
DB_PORT=3306
DB_DATABASE=laravel_eb
DB_USERNAME=admin
DB_PASSWORD=********
:

次にDB接続を手軽に試すためチュートリアルにあるようindex.phpを修正します。
// DB Connection Testのところから3行分です。

eb-laravel/public/index.php
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

// DB Connection Test
if(DB::connection()->getDatabaseName()){
        echo "Connected to database ".DB::connection()->getDatabaseName();
}

$response->send();

$kernel->terminate($request, $response);

修正したらElastic Beanstalk用にソースをZIPに固めます。

[ec2-user@ip-172-31-29-127 eb-laravel]$ zip ~/laravel-v1-1.zip -r * .[^.]* -x "vendor/*"

これをTeraTermならSSH SCPなどで取得します。

Elastic BeanstalkのLaravel向け環境設定

Laravelはご存じの通りdocument_rootはpublic配下です。
これをElastic Beanstalkにも定義します。

ダッシュボードの設定からソフトウェアの[変更]ボタンをクリックし
TOPにあるドキュメントのルートを/publicに変更し保存します。

20.png

保存するとElastic Beanstalkが環境変更の取り込みを行うのでしばし待ちます。
16.png

これでElastic Beanstalk側の準備が整ったので、[アップロードとデプロイ]ボタンより
先ほどZIP化したソースをアップロードしデプロイします。
14.png

21.png

しばし処理が走ったのちにURLにアクセスすると御覧の通り
Laravelの初期Home画面と左上に仕込んだDBConnectionのサンプルコードの実行結果が表示されます。

22.png

最後に

あくまで実行環境ですので、artisan migrateなどは他のEC2インスタンスから実行した後にこちらにリリースするイメージでしょうか

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

Windows10にXAMPPを利用してLaravel環境を作成する

  1. XAMPPをインストール
    https://www.apachefriends.org/jp/index.html
    インストール後、Apacheサーバを起動。

  2. PHPのパスを通す
    コントロールパネル > システム > システムの詳細設定 > [環境変数] を開き(デスクトップの検索から「環境」を入力してもOK)、「Path」 > [編集] > [新規]でXAMMPをインストールしたフォルダの中にある「PHP」フォルダを追加。
    例: C:\xampp\php

  3. コマンドプロンプトでPHPのバージョンを確認

    > php -v
    

    で表示される情報がPHP 7以上であることを確認。

  4. Composerのインストーラーをダウンロード
    https://getcomposer.org/
    からComposer-Setup.exeをダウンロードして実行。
    インストール先は自動で見つけて来てくれるようだが、見つけてくれない場合には自分がインストールしたXAMPPのPHPフォルダを指定(例:C:\xampp\php)。

  5. コマンドプロンプトからComposerの確認
    コマンドプロンプトを立ち上げ直し、以下コマンドを実行した際にComposerの情報が表示されるかを確認

    > composer
    
  6. htdocsフォルダまで移動

    > cd C:\xampp\htdocs
    

    の実行などで、XAMPPのhtdocsフォルダまで移動。

  7. 以下コマンドの実行でLaravel5.5.*を「csm」という名前のディレクトリに設置

    > composer create-project laravel/laravel cms --prefer-dist "5.5.*"
    

    ※PCの能力や回線速度によって完了するまでの時間は異なります(5分~10分程度)

  8. 以下のようにset successfully.が最後に表示されたら完了
    Application key [base64:xxxxxxxxxxxxxxxxxxxxxxxxxxxxx+w/Fo5y/C5Fy+Q=] set successfully.

  9. WebブラウザからLaravelの動作を確認
    URLとしてhttp://localhost/cms/public/ (自分が指定した名前やサーバにより異なります)等を指定し、Laravelの画面が表示されることを確認。
    laravel.png

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

Service層を意識したLaravelのMVCモデル

はじめに

Laravelにおいて個人的な開発であればMVCモデルを意識して行えば問題ないかと思いますが、大規模なサービス開発や長期的に運用するはよりレイヤーの構造を意識した開発を行うことが、安定したサービスを運用することに大事かと思います。
そこで今回はService層を意識したLaravelのMVCモデルについて概念のおさらいをしていきます。

Service層について

そもそもサービス層とは??

スクリーンショット 2020-02-24 15.04.08.png
P of EAA: Service Layer
Laravelの公式ドキュメントに以下の記載がありました。

Service providers are the central place of all Laravel application bootstrapping. Your own application, as well as all of Laravel's core services are bootstrapped via service providers.

引用:Service Providers - Laravel - The PHP Framework For Web Artisans

「サービスプロバイダはLaravelアプリケーション内の全てのserviceがbootstrapされる場所である」と述べられています。
serviceとは各クラスからから提供される機能と頭の片隅に入れておくと良いかと思います。また、bootstrapするというのは、イベントリスナ、ミドルウェア、ルートなどLaravelアプリケーションに必要な全てを起動するということになります。

構成とそれぞれの役割

スクリーンショット 2020-02-24 14.54.05.png
ルーティング:ルーティング、認証、フィルタ
コントローラー:HTTPリクエスト、リクエスト、レスポンス
サービス:事前条件検証、ビジネスロジック
モデル:データーベースアクセス、エンティティ固有の処理

結局サービス層ではなにをするのか

ビジネスルールに関わる処理を記載します。
ビジネスドメイン、事前条件検証とビジネスロジック、HTTP関連以外(arrayやobjectで渡す)などです。
例えばECで言うと、在庫がないと注文を確定できない等、ビジネスルールに即して注文確定させるような主処理などです。

終わりに

概念的なものだけになってしまったので次回あたりで実際どんな感じに使うのかまとめていきます。
参考:
Laravel で Service 層を取り入れるときに検討したいこと - Qiita
レイヤードアーキテクチャを意識したPHPアプリケーションの構築

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

Address in mailbox given [] does not comply with RFC 2822

以下のエラーが出た。

[2020-02-24 15:10:55] local.ERROR: Address in mailbox given [Yuz] does not comply with RFC 2822,
3.6.2. {"exception":"[object] (Swift_RfcComplianceException(code: 0): Address in mailbox given [Yuz] does not comply with RFC 2822,
3.6.2. at /var/www/html/mama-rich.net/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/MailboxHeader.php:355)
[stacktrace]

うーん。普通のメアドなのでおかしくないと思うんだけど。
まぁ、でもエラーが出ると気持ちわるいのでエラーが出ないようにしたい。

ローカル側だとアクセス権とかで触れないので、サーバーから。vimで。

vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/MailboxHeader.php

/**
 * Throws an Exception if the address passed does not comply with RFC 2822.
 *
 * @param string $address
 *
 * @throws Swift_RfcComplianceException If invalid.
 */
private function assertValidAddress($address)
{
    if (!$this->emailValidator->isValid($address, new RFCValidation())) {
        // throw new Swift_RfcComplianceException(
        //     'Address in mailbox given ['.$address.'] does not comply with RFC 2822, 3.6.2.'
        // );
    }
}


で、キャッシュをクリアしましょと。

php artisan config:clear
php artisan config:cache

これでエラーがでなくなる。

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

Laravel Graphql Server-JWTパスポート認証

これは先週のチュートリアルの続きです。

This is a continuation of last weeks tutorial.

始めましょう

コンポーザー経由でパスポートをインストールする

Install passport via composer

  docker run --rm -v $(pwd):/app composer require laravel/passport

移行を実行してパスポートをインストールする

Run migrations and install passport

  sudo docker-compose exec app-server php artisan migrate
  sudo docker-compose exec app-server php artisan passport:install

これで、お気に入りのエディター(私の場合はPHPStorm)を使用してプロジェクトを開き、 Userモデルを編集してHasApiTokens特性を追加できます。

We can now open the project using our favorite editor, in my case PHPStorm, and edit the User model to add the HasApiTokens trait.

  <?php

  namespace App;

  use Illuminate\Contracts\Auth\MustVerifyEmail;
  use Illuminate\Foundation\Auth\User as Authenticatable;
  use Illuminate\Notifications\Notifiable;
  use Laravel\Passport\HasApiTokens;

  class User extends Authenticatable
  {
      use HasApiTokens, Notifiable;

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

config \ auth.phpでApi認証プロバイダーをパスポートに変更します

We change the Api Auth provider to passport in the config\auth.php

  'guards' => [
          'web' => [
              'driver' => 'session',
              'provider' => 'users',
          ],

          'api' => [
              'driver' => 'passport',
              'provider' => 'users',
              'hash' => false,
          ],
      ],
Graphqlを構成する

職人を使用して新しい突然変異を作成する

create a new mutation using artisan

php artisan lighthouse:mutation Login

mutationsフォルダー内で、Login.phpを開き、invoke」関数のコンテンツを置き換えます

Inside the mutations folder, open the Login.php and replace the invoke function content

if(Auth::attempt($args)){
            $user = Auth::user();
            $success['token'] =  $user->createToken('MyApp')->accessToken;
            return $success;
        }

schema.graphqlのリストに新しく作成されたミューテーションを追加します

Add the newly created mutation in the list on the schema.graphql

type Mutation {
    createUser(name: String!, email: String!, password: String!): User
    Login(email: String!, password: String!): Token
}

type Token{
    token:String!
}

新しい突然変異を使用してログインします

Make a login using the new mutation

mutation{
  Login(email:"my@email.com",password:"mypassword"){
    token
  }
}

トークンをテストするには、 auth:apiミドルウェアを追加して、schema.graphql内のクエリの1つを変更します

To test the token, modify one of the queries inside the schema.graphql, by adding the auth:api middleware

type Query {
    allusers: [User!]! @all
    users: [User!]! @paginate(defaultCount: 10)
    user(id: ID @eq): User @middleware(checks:["auth:api"]) @find
}

新しいクエリリクエストを行うときは、忘れずに認証ベアラートークンヘッダーを追加してください

When you make the new query request, remember to add the Auth Bearer token header

Authorization Bearer: eyaz.........
query{
  user(id:1){
    id,
    name,
    email
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel フォームリクエストのauthorize()を活用してみる

よくよく考えれば当り前なんだけど、解説されてなくて気づけなかった...

フォームリクエストとは?

バリデーション 5.8 Laravelのフォームリクエストバリデーションセクションに説明があります。
HTTPリクエストをバリデーションしたりできるものです。

フォームリクエストクラスのできること

Requestクラスで使えるメソッドがすべて使える

$this->input('hoge');

// /posts/{post} とかになってるURLの{post}の部分にアクセス
$this->post;

Requestを継承しているのでもちろん使えるんですよね(自戒)
{post}にあたる部分の名前に自信がない場合はphp artisan route:listで確認できます。

authorize()のできること

bool型で認証の可否を判断

trueを返せば認証が成功したとして、処理を継続する。
falseを返せば認証に問題があるとして、403を返す。

DIされられる

public function authorize(HogeRepository $repository){
    $repository->fuga();
    // ...
    return $bool;
}

クラスをDIして、それを使ったオレオレ認証処理を作れる。

組み合わせる

こんなテーブルがあるとする

カラム名 役割
id 主キー
message メッセージ
edit_key ハッシュ化された編集キー

フォームでメッセージと編集キーを入力すると、データベースにそれが保存され、それを消したり、編集したりしたくば編集キーを使って認証をするという感じ。
URLは/messages/{message}として、HTTPリクエストヘッダに編集キーを付け、PUTリクエストでアクセスされたものと想定。

authorize()メソッドで認証

public function authorize(){
   $model = Message::findOrFail($this->message);
   return Hash::check($this->header('edit-key', $model->edit_key);
}

$this->messageにはレコードのIDを含ませる。
そこからレコードを取得し、保存していたハッシュ化された編集キーと送信された生の編集キーをHash::check()で照合する。
Hash::check()は2値が同じものであればtrueを、異なるものであればfalseを返すので、これに認証の可否を任せています。

もっと複雑な認証ロジックを実装する場合は、それを他のクラスへ切り出し、authorize()へDIしてやればスッキリさを維持しつつ、理想的な処理を行えると思います。


Laravelはとても便利だ... ありがたや...

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