ポートフォリオ「ToDoList」について

#初めに
はじめまして。
今回、Laravelの基礎のアウトプットを兼ねてポートフォリオを作成しましたので、説明します。

#目的
フレームワークLaravelを用いての開発をすることで、下記事項が理解できているかを確認する

・MVCモデル
・Bladeを用いたviewの実装
・フォームリクエストを用いたバリデーションの実装
・Eloquent ORMを用いたCRUDの実装

#スペック
■言語
PHP 7.1.23
JavaScript  (jQuery)
HTML
CSS  (SASS)

■DBMS
MySQL 5.5.60

■フレームワーク
Laravel Framework 5.8.19

■サーバー
AWS EC2 
(DBはRDS、ドメイン、SMTPはXSERVERを使用)

#ポートフォリオの概要
ToDoリスト
PCデザイン(960px以上)
レスポンシブデザイン(500px以下のスマートフォン用)

#ポートフォリオの機能
■ユーザー情報関連
・ユーザー登録
・ログイン・ログアウト
・メールアドレス変更
・パスワード変更
・パスワードリマインダー
・退会

■メイン機能
・タスク一覧表示
・タスク新規作成
・タスク完了処理
・タスク内容編集
・タスク検索
・完了タスクの一覧表示
・完了タスクの検索
・完了タスクの復元処理  (誤って完了させた場合の処置)
・カテゴリー編集

■その他
・お問い合わせ

#何ができるか
■タスクの追加
![タスク作成.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415296/eff445a3-4485-1b1c-ddac-c29831cb0c45.gif)


■タスクの完了・復元処理
(誤って完了した際は復元できるようにしている)
![タスク完了・復元.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415296/7c8002c3-8f0b-34e4-7204-e275b153bcde.gif)


■タスク編集処理
![タスク編集.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415296/c29e36ce-683f-e76d-ef3e-9a0c9c441380.gif)


■タスク検索
(完了後のタスクも検索可能)
![タスク検索.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415296/a5e3d8cd-8a37-01d2-c029-5690db2a30a3.gif)


#作製方法

###作製時間 
計：105h
1.機能洗い出し :1h
2.画面モック作成 :5h
3.テーブル設計、DB作成 :3h
4.各画面、機能を実装:66h
5.ローカル環境テスト:2h
6.デプロイ:26h
7.本番環境テスト:2h


###作製手順

1.機能の洗い出し:1h

2.画面モック作成:5h
![スクリーンショット 2019-06-03 16.12.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415296/2bbebf25-9931-cd3f-16e1-bbe221948826.png)
![スクリーンショット 2019-06-03 16.11.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415296/03d8c040-391f-7182-063c-274a3708d49d.png)


3.テーブル設計、DB作成:3h
マイグレーションにてテーブル作成
![スクリーンショット 2019-06-03 18.59.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415296/133d7507-7354-8e9e-623a-c53a9040691e.png)




4.各画面、機能を実装:66h

①画面モックを元に、html,css,JavaScriptで画面のみ作成　14h
(SASS、FLOCSS設計を使用。)

```FLOCSS.
├──foundation/
|    ├──reset.scss
|    ├──mixin.scss
|    ├──variable.scss
|    ├──header.scss
|    ├──footer.scss    
|
├──layout/
|    ├──background.scss  
|
└──object/
    ├──component/
    |    ├──form.scss
    |    ├──btn.scss
    |
    ├──project/
    |    ├──top.scss
    |    ├──task.scss
    |    ├──sidebar.scss
    |    ├──mymenu.scss
    |
    └──utility/
         ├──modal.scss
         ├──err.scss
         ├──accordion.scss
         ├──pagination.scss
         ├──fadein.scss
         ├──link.scss
```

レスポンシブデザインも対応
![レスポンシブ.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415296/b7028bae-f1f5-bbfa-0788-08b56dfe39e1.gif)



②画面をBladeに反映 : 4h

③ログイン、ユーザー登録機能などユーザー情報周りから作成。 : 8h

④CRUDを用いたメイン機能を実装(Eloquentを使用)    :12h
タスク一覧表示にはtasksテーブルとcategoriesテーブルをリレーションしてデータを取得している。
(belongsTo結合)

```Task.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
    protected $guarded =  array('id');

    public function getTaskName()
    {
        return $this->task_name;
    }

    public function category()
    {
        return $this->belongsTo('App\Category','category_id','id');
    }
}

```

⑤例外処理、バリデーションの実装  :16h

⑥メール送信処理、退会処理等の実装 :12h

5.ローカル環境テスト:2h

・各機能の処理が正常終了すること。
・バリデーションの確認
・ログイン情報が保持されていること。(ログアウト後は破棄されていること)
・例外発生時、指定のエラーページに遷移すること。
・スマホサイズ(500px以下)にてレイアウトが崩れないこと。など

6.本番環境へデプロイ:26h

①AWS EC2にてインスタンス作製、セキュリティグループ設定
②EC2インスタンスにLAMP、composer、Laravelをインストール
③configファイルなど設定を変更
④ポートフォリオをアップロード
⑤RDSにDBを構築
⑥MySQLのパーミッション設定
⑦マイグレーションを実行
⑧xserverのSMTPにてメールアカウントを設定


7.本番環境環境テスト:2h

ローカル環境のテストケース　+ 実機確認(PC:Chrome, スマートフォン：iOS)


#工夫したところ(機能面)
###完了、編集、復元処理後の検索条件の保持
![qiita_検索条件保持.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415296/09547c89-77bb-2162-a947-26054a554ae7.gif)


###メニュー画面をアコーディオンメニューにて実装
![qiita_アコーディオン.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415296/bb721a48-c1ef-ebc3-c14f-fe2b0038130a.gif)


###処理後にテロップを表示
![qiita_テロップ.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415296/f7e435e5-fe4f-7d1b-2e9f-7b261b4c5676.gif)


#工夫したところ(実装面)

###バリデーション機能をフォームリクエストに実装
![バリデーション.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415296/1517f0fd-605a-da6a-76d8-8472dcc22f9f.gif)


```SignUpRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class SignUpRequest extends FormRequest
{
    public function authorize()
    {
        if($this->path() == 'signup'){
            return true;
        } else {
            return false;
        }        
    }

    public function rules()
    {
        return [
            'name' => 'required|between:0,10',
            'email' => 'required|email|between:0,255|unique:users',
            'password' => 'required|alpha_num_check|size:8|confirmed'
        ];
    }

    public function messages()
    {
        return [
            'name.required' => '入力必須です。',
            'name.between' => '10文字以内で入力してください。',
            'email.required' =>'入力必須です。',
            'email.email' =>'メールアドレスの形式で入力してください。',
            'email.between' => '255文字以内で入力してください。',
            'email.unique' =>'このメールアドレスはすでに使用されています。',
            'password.required' =>'入力必須です。',
            'password.alpha_num_check' =>'半角英数字にて入力してください。',
            'password.size' =>'8桁にて入力してください。',
            'password.confirmed' => 'パスワードが確認欄と一致していません。'
        ];
    }
}

```

###カスタムバリデータを実装
(半角英数字チェックなどLaravel標準にないバリデーションを自作)

```CustomValidator.php
<?php
namespace App\Http\Validators;

use Illuminate\Validation\Validator;
use App\User;
use Illuminate\Support\Facades\Auth;

class CustomValidator extends Validator
{
    public function validateAlphaNumCheck($attribute, $value, $parameters)
    {
        return preg_match('/^[A-Za-z\d]+$/', $value);
    }

    public function validatePassVerifi( $attribute, $value, $parameters){
        $id = Auth::id();
        $user = User::where('id', $id)->first();

        if (Auth::attempt(['email' => $user->email, 'password' => $value])) {
            return true;
        } else {
            return false;
        }
    }

    public function validateSameEmailVerifi($attribute, $value, $parameters){
        $user = User::where('email', $value)->where('delete_flg',false)->count();

        if($user == 0){
            return false;
        } else{
            return true;
        }
    }

    public function validateSameAuthKeyVerifi($attribute, $value, $parameters){
        $auth_key = session()->get( 'auth_key');
        if($auth_key !== $value){
            return false;
        } else{
            return true;
        }
    }
}
```

※カスタムバリデータはサービスプロバイダに設定

```ValidationServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Validators\Validator;
use App\Http\Validators\CustomValidator;

class ValidationServiceProvider extends ServiceProvider
{
    public function register(){}

    public function boot()
    {
        $validator = $this->app['validator'];
        $validator->resolver(function($translator, $data, $rules, $messages) {
            return new CustomValidator( $translator, $data, $rules, $messages);
        });
    }
}
```
###例外処理をhandlerクラスのrenderメソッドにまとめて実装
※例外が発生した場合、専用のエラー画面に遷移するようにしている
(テストのため、メール送信機能にてエラーが発生するようにしている)
![例外処理.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415296/eb463dab-fb66-a8d8-6371-bf0f7feb6282.gif)


```Handler.php
<?php

namespace App\Exceptions;

use Exception;
use ErrorException;
use \Illuminate\Database\QueryException;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;

class Handler extends ExceptionHandler
{
    protected $dontReport = [];

    protected $dontFlash = [
        'password',
        'password_confirmation',
    ];

    public function report(Exception $exception)
    {
        parent::report($exception);
    }

    public function render($request, Exception $exception)
    {
        if ($exception instanceof QueryException 
        || $exception instanceof \Swift_TransportException 
        || $exception instanceof NotFoundHttpException
        || $exception instanceof MethodNotAllowedHttpException 
        || $exception instanceof FatalThrowableError 
        || $exception instanceof ErrorException) {
            return response()->view( 'error', ['exception' => $exception]);
        }

        return parent::render($request, $exception);
    }
}

```

###ログイン認証をミドルウェアに記述
ルーティングに記述することで、指定したアクションに遷移した際に認証処理を実施する

＊ログイン前の機能用のログイン認証ミドルウェア

```BeforeLoginMiddleware.php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;

class BeforeLoginMiddleware
{
    public function handle($request, Closure $next)
    {
        if (Auth::check()) {
            return redirect()->action('TaskController@index');
        }
        return $next($request);
    }
}

```

```web.php
<?php

use App\Http\Middleware\BeforeLoginMiddleware;

// ログイン前
Route::get('/', 'IndexController@index')->middleware(BeforeLoginMiddleware::class);

//・・・・・・・

```



#ポートフォリオの課題

・ログイン処理がなくともメイン機能を見られるようにする(ポートフォリオとして簡単に確認できるようにする)
・完了、復元、編集処理後に検索条件の保持はできているが、ページングの保持ができていない。
・タスクの期限、通知機能の追加
・URLのhttps化
・make:authにて自動でユーザー情報周りの機能を実装
・モデルにスコープをつける(whereなどはコントローラに記述しない)
・DBにindexを設定(マイグレーションに実装)
・完了、復元機能の処理が重い
　→　DB情報をキャッシュに格納して使用する。
　→  Ajaxを使用
　→　jQueryまたはVue.jsなどを用いてDOMの入れ替えのみをする。など


#現在の学習内容(2019.06.07現在)
・JavaScriptの基礎
・React、Vue.js（主にVue.js）
・Linuxの環境構築(ローカルの自分の端末上にて。シェルスクリプトを触るくらいを目指す)