20200518のlaravelに関する記事は8件です。

Laradockを使った開発環境の構築方法

nginx mysqlを使用して進めます。

ローカルにファイルを作成

最終的に以下の構造になります。

app
├── app
└── laradock

laradockのインストール

git clone https://github.com/Laradock/laradock.git
cd laradock
cp env-example .env
mkdir ../app

laradockをgithubからインストールし、設定ファイルをコピーします。
また、ローカルのフォルダも作成しておきます。

.envを編集

APP_CODE_PATH_HOST=../app

ローカルで編集できるようにするためAPP_CODE_PATH_HOSTの設定を変えます。

Laradockのコンテナ起動

$ docker-compose up -d nginx mysql

Recreating laradock_docker-in-docker_1 ... done
Recreating laradock_mysql_1            ... done
Recreating laradock_workspace_1        ... done
Recreating laradock_php-fpm_1          ... done
Recreating laradock_nginx_1            ... done

workspaceコンテナのパッケージのアップデート

docker exec -it laradock_workspace_1 bash

でコンテナの中に入ります。
-itオプションは以下の意味です。
-i アタッチしていなくても STDIN をオープンにし続ける(標準入力を使えるようにする)
-t 疑似ターミナル (pseudo-TTY) の割り当て(ターミナルの形で表示させる)

apt-get update

仮想OSをupdateします。

laravelのプロジェクトの作成

composer create-project laravel/laravel .

仮想OSのプロジェクト直下にlaravelのプロジェクトを作成します。
APP_CODE_PATH_HOSTの設定を変えたため、実際は、../appに作成されます。

MySQLの設定

ローカルに作成されたlaravelプロジェクト内の.envを編集します。

DB_CONNECTION=mysql
DB_HOST=laradock_mysql_1
DB_PORT=3306
DB_DATABASE=default
DB_USERNAME=default
DB_PASSWORD=secret

localhostにアクセスしてみる

localhostにアクセスしてみましょう。

Screen Shot 2020-05-18 at 16.43.07.png

この画面が表示されれば、うまく行っています。

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

【Laravel】現在のURLからメニューをActiveにする

やりたいこと

Bootstrapでメニューに現在のURLからactiveクラスを付与したい

適当にルーティング用意

web.php
Route::get('/hoge','HogeController@index')->name('hoge');
Route::get('/fufa/{fuga_id}','FugaController@index')->name('fuga');
Route::get('/piyo/piyo','PiyoController@index')->name('piyo');

メニューのサンプル

<li>
    <a href="{{ route('hoge') }}">
        hoge
    </a>
</li>
<li class="active">
    <a href="{{ route('fuga',['fuga_id' => 1]) }}">
        fuga
    </a>
</li>
<li>
    <a href="{{ route('piyo') }}">
        piyo
    </a>
</li>

やり方

1

{{ request()->route()->named('hoge') ? 'active' : '' }}
{{ request()->route()->named('fuga') ? 'active' : '' }}
{{ request()->route()->named('piyo') ? 'active' : '' }}

現在のリクエストが指定した名前付きルートのものであるかを判定したい場合は、Routeインスタンスのnamedメソッドを使います。
これが一番良い方法
必須パラメータ除いても判定できる

2

{{ reqeust()->is('hoge') ? 'active' : '' }}
{{ reqeust()->is('fuga/*') ? 'active' : '' }}
{{ reqeust()->is('piyo/piyo') ? 'active' : '' }}

isメソッドにより、リクエストのURIが指定されたパターンに合致するかを確認できます。このメソッドでは*をワイルドカードとして使用できます。
変更に弱い。URL変えるたびにこちらも変える必要がある

参考

https://quickadminpanel.com/blog/laravel-how-to-make-menu-item-active-by-urlroute/

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

LaravelのIlluminate\Http\Requestについてメモ

hasメソッド

if ($request->has('name')) {
    //
}

if ($request->has(['name', 'email'])) {
    //
}

if ($request->filled('name')) {
    //
}

Illuminate\Http\Requestクラスのhasメソッドは、リクエストに値が存在する場合に、trueを返します。

配列を指定した場合、hasメソッドは指定値がすべて存在するかを判定します。

値がリクエストに存在しており、かつ空でないことを判定したい場合は、filledメソッドを使います。

@if(Request::has('confirm'))

@endif

bladeの中で使うならこんな感じ

flashメソッド

$request->flash();

$request->flashOnly(['username', 'email']);

$request->flashExcept('password');

入力をフラッシュデータとして保存します。

Illuminate\Http\Requestクラスのflashメソッドは、現在の入力をセッションへ、アプリケーションに要求される次のユーザーリクエストの処理中だけ利用できるフラッシュデータとして保存します。

セッションへ入力の一部をフラッシュデータとして保存するには、flashOnlyとflashExceptが使用できます。両メソッドは、パスワードなどの機密情報をセッションに含めないために便利です。

oldメソッド

$username = $request->old('username');
<input type="text" name="username" value="{{ old('username') }}">

直前のリクエストのフラッシュデータを取得するには、Requestインスタンスに対しoldメソッドを使用してください。oldメソッドはセッションにフラッシュデータとして保存されている入力を取り出すために役に立ちます。

Laravelではoldグローバルヘルパ関数も用意しています。特にBladeテンプレートで直前の入力値を表示したい場合に、oldヘルパは便利です。指定した文字列の入力が存在していないときは、nullを返します。

参考

HTTPリクエスト 5.5 Laravel

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

Laravel リレーションの覚え方

混乱する人向け。

手っ取り早い考え方

  • Q1 どっちがbelongsTo()?

    • A. 外部キー(FK)がある方がbelongsTo()
  • Q2. hasMany(),hasOne()どっちを使えばいい?

    • A. belongsTo()側のTBLに、対象FKのレコードが複数回登場するならhasMany()。uniqueならhasOne()

Sample Database Table

アカウント情報

users TBL
id autoincrement PK
email string
password string
name string

アカウントユーザの住所

address TBL
id autoincrement PK
user_id FK users.id
prefecture_code string
address string

掲示板投稿

posts TBL column
id autoincrement PK
user_id FK users.id
title string

掲示板投稿に対するコメント

comment TBL column
id autoincrement PK
post_id FK posts.id
user_id FK users.id
message string

User Model

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected $table = 'users';   

        public function address()
    {
        return $this->hasOne('App\Models\Address', 'user_id');
    }

    public function posts()
    {
        return $this->hasMany('App\Models\Post', 'user_id');
    }

    public function comments()
    {
        return $this->hasMany('App\Models\Comment', 'user_id');
    }

}

Address Model

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Address extends Model
{
    protected $table = 'address';   

    public function user()
    {
        return $this->belongsTo('App\Models\User', 'user_id');
    }
}

Post Model

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected $table = 'posts';   

    public function user()
    {
        return $this->belongsTo('App\Models\User', 'user_id');
    }

    public function comments()
    {
        return $this->hasMany('App\Models\Comment', 'post_id');
    }
}

Comment Model

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    protected $table = 'comments';   

    public function user()
    {
        return $this->belongsTo('App\Models\User', 'user_id');
    }

    public function post()
    {
        return $this->belongsTo('App\Models\Post', 'post_id');
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelでのJWT認証

Laravel + tymon/jwt-auth にて認証つきAPIサーバーを構築する

いろいろな情報を参照させて頂き試したが、AccessTokenが切れた状態 & RefreshTokenの期限が切れていない状態でのトークンの更新ができなかったので、少し工夫したところも記載しています。

なお、ここで構築した環境は以下にてアクセスできますのでご参照ください。
GitHub: https://github.com/sankosc/apitest-server
サーバー: https://develop.sankosc.co.jp/apitest/

環境

  • PHP 7.4.5
  • Laravel Framework 7.10.3
  • 10.4.11-MariaDB
  • composerとnpmはインストール済み

準備

Laravelのインストール

$ composer create-project laravel/laravel [プロジェクト名] --prefer-dist

データベース作成

  • PhpMyAdminで作成 ( utf8mb4_bin )
  • .env編集 DB_DATABASE=[データベース名]

Laravelの認証機能を有効にする

$ composer require laravel/ui
$ php artisan ui vue --auth
$ npm install
$ npm run dev

マイグレーション

$ php artisan migrate

認証API構築

JWT認証ライブラリインポート

$ composer require tymon/jwt-auth
$ php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
$ php artisan jwt:secret

ユーザーモデル編集

app/User.php
<?php

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Tymon\JWTAuth\Contracts\JWTSubject;

class User extends Authenticatable implements JWTSubject
{
//
// ~~ 省略 ~~ 
//

    /**
     * Get the identifier that will be stored in the subject claim of the JWT.
     *
     * @return mixed
     */
    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

    /**
     * Return a key value array, containing any custom claims to be added to the JWT.
     *
     * @return array
     */
    public function getJWTCustomClaims()
    {
        return [];
    }
}

config/auth.phpを編集

config/auth.php
'defaults' => [
    'guard' => 'api',
    'passwords' => 'users',
],

'guards' => [
    'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
    ],
],

route追加

routes/api.php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::post('login', 'Api\AuthController@login');
Route::get('refresh', 'Api\AuthController@refresh');
Route::group(['middleware' => ['jwt.auth']], function () {
    Route::post('logout', 'Api\AuthController@logout');
    Route::get('me', 'Api\AuthController@me');
});

AuthController作成

app/controllers/api/AuthController.php
<?php

namespace App\Http\Controllers\Api;

use Illuminate\Http\Request;
use Illuminate\Auth\Events\Registered;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;

class AuthController extends Controller
{
    /**
     * Create a new AuthController instance.
     *
     * @return void
     */
    public function __construct()
    {
//        $this->middleware('auth:api', ['except' => ['login']]);
    }

    /**
     * Get a JWT via given credentials.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function login()
    {
        $credentials = request(['email', 'password']);

        if (! $token = auth()->attempt($credentials)) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }

        return $this->respondWithToken($token);
    }

    /**
     * Get the authenticated User.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function me()
    {
        return response()->json(auth()->user());
    }

    /**
     * Log the user out (Invalidate the token).
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function logout()
    {
        auth()->logout();

        return response()->json(['message' => 'Successfully logged out']);
    }

    /**
     * Refresh a token.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function refresh()
    {
        try {
            return $this->respondWithToken(auth()->refresh());
        } catch (\Tymon\JWTAuth\Exceptions\JWTException $e) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }
    }

    /**
     * Get the token array structure.
     *
     * @param  string $token
     *
     * @return \Illuminate\Http\JsonResponse
     */
    protected function respondWithToken($token)
    {
        return response()->json([
            'access_token' => $token,
            'token_type' => 'bearer',
            'expires_in' => auth()->factory()->getTTL() * 60
        ]);
    }
}

確認

Invoke-RestMethod -Uri "http://localhost/api/login" -Method POST -Body "email=sample@sankosc.co.jp&password=sample123"

テスト用のAPI作成

認証なしのget API helloとpost API echoを作成します。

コントローラ作成

app/controllers/api/TestController.php
<?php

namespace App\Http\Controllers\APi;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class TestController extends Controller
{
    public function hello()
    {
        return response()->json([
            'message' => 'hello'
        ]);
    }

    public function echo(Request $request)
    {
        return response()->json([
            'message' => $request->input('message')
        ]);
    }
}

route追加

routes/api.php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::post('login', 'Api\AuthController@login');
Route::get('hello', 'Api\TestController@hello');
Route::post('echo', 'Api\TestController@echo');
Route::get('refresh', 'Api\AuthController@refresh');

Route::group(['middleware' => ['jwt.auth']], function () {
    Route::post('logout', 'Api\AuthController@logout');
    Route::get('me', 'Api\AuthController@me');
});

AccessTokenのRefreshについて

AccessTokenが切れた状態かつ、RefreshTokenの期限が切れていない状態でのトークンの更新する方法がどうしてもわかりませんでした。
jwt.refresh をroutes/api.phpにて設定するように思えるのですがうまく動作しません。
なので、公式のQuick startで紹介されている方法から以下の2点を変更しています。

  1. route/api.phpにてrefreshを認証の外におく
  2. AuthControllerにてrefreshの処理を少し修正
routes/api.php(変更前:refreshが認証の中にいる)
Route::post('login', 'Api\AuthController@login');
Route::get('hello', 'Api\TestController@hello');
Route::post('echo', 'Api\TestController@echo');

Route::group(['middleware' => ['jwt.auth']], function () {
    Route::post('logout', 'Api\AuthController@logout');
    Route::get('refresh', 'Api\AuthController@refresh');
    Route::get('me', 'Api\AuthController@me');
});
routes/api.php(変更後:refreshが認証の外にいる)
Route::post('login', 'Api\AuthController@login');
Route::get('hello', 'Api\TestController@hello');
Route::post('echo', 'Api\TestController@echo');
Route::get('refresh', 'Api\AuthController@refresh');

Route::group(['middleware' => ['jwt.auth']], function () {
    Route::post('logout', 'Api\AuthController@logout');
    Route::get('me', 'Api\AuthController@me');
});

AuthController.refresh修正

app/controllers/api/AuthController.php(変更前)
    public function refresh()
    {
        return $this->respondWithToken(auth()->refresh());
    }

AuthController.refresh修正

app/controllers/api/AuthController.php(変更後)
    public function refresh()
    {
        try {
            return $this->respondWithToken(auth()->refresh());
        } catch (\Tymon\JWTAuth\Exceptions\JWTException $e) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }
    }

こうすることで、完全に認証がない状態だと401を返し、RefreshTokenの期限が切れる前であれば、更新したトークン返すようになります。

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

「SQLSTATE[HY000] [2002] Connection refused」エラーの解決法

sql.png

Cloud9でLaravelを使った開発をしていたら画像のようなエラーが出ました。

初めは何かわからなかったけど、原因と対策は簡単。

コマンドで初めにSQLサーバーを起動していなかったというだけでした。

開発環境

・Windows10
・AWS(Cloud9)
・Larabel

解決法

初めにSQLサーバーを起動することが必要です。

◎SQLサーバ起動

$ sudo service mysqld start

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

composerでrequireなどをするとインストールに失敗する

目的

  • require時に発生するエラーを解決した話をまとめる

実施環境

  • ハードウェア環境(下記の二つの環境で確認)
項目 情報
OS macOS Catalina(10.15.3)
ハードウェア MacBook Pro (16-inch ,2019)
プロセッサ 2.6 GHz 6コアIntel Core i7
メモリ 16 GB 2667 MHz DDR4
グラフィックス AMD Radeon Pro 5300M 4 GB Intel UHD Graphics 630 1536 MB
  • ソフトウェア環境
項目 情報 備考
PHP バージョン 7.4.3 Homwbrewを用いて導入
Laravel バージョン 7.0.8 commposerを用いて導入
MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いて導入

エラー内容

  • laravel/uiをインストールしようと思い下記のコマンドを実行した。

    $ composer require laravel/ui
    
  • 下記の様なエラーが発生しインストールが失敗してしまった。

    >Using version ^2.0 for laravel/ui
    >./composer.json has been updated
    >Loading composer repositories with package information
    >Updating dependencies (including require-dev)
    >Nothing to install or update
    >Generating optimized autoload files
    >> Illuminate\Foundation\ComposerScripts::postAutoloadDump
    >> @php artisan package:discover --ansi
    >Discovered Package: facade/ignition
    >Discovered Package: fideloper/proxy
    >Discovered Package: fruitcake/laravel-cors
    >Discovered Package: laravel/tinker
    >Discovered Package: laravel/ui
    >Discovered Package: nesbot/carbon
    >Discovered Package: nunomaduro/collision
    >Package manifest generated successfully.
    >10 packages you are using are looking for funding.
    >Use the `composer fund` command to find out more!
    
    >Installation failed, reverting ./composer.json to its original content.
    

原因

  • すでにインストール済みのものをインストールしようとしてしまっていた。
  • そもそも実行する必要のないコマンドを実行してエラーが出てしまっていた。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CakePHP2.0 を、Laravel, GitHub Actions, ECS, CloudFormation にしました

4ヶ月かかりました。

手動デプロイの CakePHP2.0 を Laravel, GitHub Actions, ECS, CloudFormation に切り替えました。
ほとんど触ってないプロダクトだったので、テスト書きつつ、仕様書かきつつ...なんとか終わって嬉しいです。

この記事では、感想や困ったこと、主に感想を書きます。
感想も要約すると「さいこうー!」と言ってるだけです。ご了承ください。

感想: CakePHP → Laravel 気持ちいい

最高です。とにかく気持ちいいです。CakePHP2.0 の配列地獄から抜け出して、プロパティへのアクセスが嬉しいです。
物量があるので、がんばって全部書き直しました。
テストコードがないので、リファクタリングはほどほどにやりながら進めました。
CakePHP → Rails のときより、PHP コードが使い回しできるので楽でした。

感想: GitHub Actions 気持ちいい

CircleCI か GitHub Actions が候補でした。
社内(同事業部)で5つのサービスを運用しているのですが、他4つは全部 CircleCI です。
でも最近の GitHub Actions の盛り上がりを見て、試したくて仕方ありませんでした。

結論、最高です。

Pull request の隣に鎮座している Actions が最高です。いちいちサービスを跨がなくてよいのが本当に気持ちいいです。

スクリーンショット 2020-05-12 15.37.58.png

yaml は

  • deploy-production.yml
  • deploy-staging.yml
  • lint.yml
  • test.yml

といくつか作りました。

ECS をデプロイする際に、https://github.com/aws-actions がありますから、ここの actions を
使えば自分では何も考えずに済む点が良いです。(後述する、migration は少し詰まりました。)

感想: CloudFormation の導入

81834982-aaaa6b00-957c-11ea-8d08-3a82cbcfde9c.png

社内では、まだインフラのコード化は進んでいません。ですが、すでに5つのサービスを運用していて、それぞれ似たようなインフラ構成ですので、コード管理して整備しておきたかったです。
CloudFormation か Terraform で迷うところですが。個人的に Terraform 使ったことないのと、CTO が CloudFormation でいいよと適当に呟いたのと、AWS リソースしか使わないので、という理由で選びました。

AWS CDK も良いという声があり、それも迷いましたが、そこは段階的に行くとして、まずは CloudFormation 導入に至りました。

結論、最高です。

これで、リソースの名前づけや、変更など綺麗にやっていけそうです。

感想: ECS にした

EKS か ECS で悩んだのですが、やっぱり簡単なので ECS にしました。社内には EKS も1つあるのですが、ECS に統一することにしました。
これで本番がコンテナ化できたので、デプロイスクリプトとさよならできて嬉しいです。ローカルも本番も docker 化することができで差分がなくなったことも嬉しい。

困ったこと: GitHub Actions のプルリクが動かない

プルリクを出したら lint や test を回すということがしたいのですが、最初それが出来ませんでした。なぜかというと、僕らの開発フローが fork したリポジトリからプルリクを出すスタイルだからです。

private リポジトリの場合、fork したリポジトリから出す pull_request ではワークフローが動きません。

https://help.github.com/en/actions/reference/events-that-trigger-workflows#pull-request-events-for-forked-repositories

なので、fork をやめました。 本体に commit して プルリクを作るスタイルに変えました。いまのところ、これで困っていません。

困ったこと: DB Migration をどうしたか

発想的には、one shot task でやるということでシンプルなのですが、GitHub Actions でどうやるんだ?というのに少しつまりました。

結論としては aws-actions/amazon-ecs-deploy-task-definition@v1 でタスク定義を更新して、 ecs run-task するという以下のようなイメージです。

# migration タスク定義の登録
- name: Register migration task def
  id: register-migration-task-def
  uses: aws-actions/amazon-ecs-deploy-task-definition@v1
  with:
    task-definition: ${{ steps.task-def.outputs.task-definition }}
    cluster: sample

#  migration タスク実行
- name: migrate
  run: aws ecs run-task
        --launch-type FARGATE --cluster sample
        --task-definition ${{ steps.register-migration-task-def.outputs.task-definition-arn }}

CloudFormation でまだよく掴めていないこと

今回初めて触りましたので、
どこまで、CloudFormation でやるのか?ということについて、答えは見えていません。
VPC、サブネット、ALB、セキュリティグループなどは、CloudFormation で作っていますが、DB インスタンス、ECS サービス、踏み台サーバインスタンスなどは、手動です。時間と体力がなく、今はこのような状態です。どこまで CloudFormation でやるのかは、今後の課題として模索していきたいと思います。

今後対応していきたいこと

まず、Log です。いまは nginx も app からなんの加工もせずに CloudWatch に流していますが、見れたものではありません。json 形式にするとか FireLens とかやっていきたいとおもいます。

つづいて踏み台サーバです。そもそも VPN でやるとか、他にもいろいろ踏み台サーバをなくす方法があると思うのですが、この辺も考えて行きたいと思います。が、BI ツールの DOMO が DB に繋げないといけなかったりして、障壁はいろいろありそうです。

そして、AWS CDK。これは、追い追いでいいかなと思っています。良い良いという声は聞こえてくるので、CloudFormation に馴染んだら検討したいと思いました。

リリース作業で改めて感謝したツール

SENTRY

もうこれがないと、やってられないと思うほど、泣くほど嬉しいツールです。案の定、リリース作業直後バグは発生しました。でも SENTRY のおかげで、エラー監視ができすぐに対処できました。本当にありがたいツールです。

おわりに

今回の構成にしてみて、アプリケーションエンジニアにとって、デプロイやインフラの見通しがよくなったと思いました。今後もどんどん開発体験は良くなっていくんだろうなぁ。すごい。

CloudFormation のサンプルコードを置いておきます。誰かのお役に立てば幸いです。
github.com/mochizukikotaro/cloudformation-sample

ありがとうございました。

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