--- title: Laravelのメモ tags: 定例発表 description: Laravelの基本 slideOptions: theme: black slideNumber: 'c/t' center: false transition: 'none' keyboard: true width: '93%' height: '150%' --- <style> /* basic design */ .reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6, .reveal section, .reveal table, .reveal li, .reveal blockquote, .reveal th, .reveal td, .reveal p { font-family: 'Meiryo UI', 'Source Sans Pro', Helvetica, sans-serif, 'Helvetica Neue', 'Helvetica', 'Arial', 'Hiragino Sans', 'ヒラギノ角ゴシック', YuGothic, 'Yu Gothic'; text-align: left; line-height: 1.6; letter-spacing: normal; text-shadow: none; word-wrap: break-word; } </style> # Laravel - PHPで書かれたWebアプリケーションフレームワーク(OSS) - らくらくWebサイトが構築できる(MVC) ![](https://i.imgur.com/FYx3XoM.png) ## Laravel とほか https://trends.google.com/trends/explore?q=CakePHP,Laravel,Codeigniter,Phalcon,Symfony - 圧倒的にLaravelが人気 - Symfony はLaravelのコアにも利用されている ## Laravel のバージョンポリシー - LTSリリースでは、バグ修正が2年間提供され、セキュリティ修正が3年間提供されます - 一般的なリリースの場合、バグ修正は6か月間、セキュリティ修正は1年間提供されます。 - 最近(3/3) にVersion 7 がリリースされた ![](https://i.imgur.com/g85cI0L.png) ## Laravelのよさげなところ - ORM使いやすい(relation 定義、scope) - Validatonが豊富 - 独自の例外処理が気軽にかける - Kernel - DIがよい ## ORM ってなに - Object-Relational Mapping (オブジェクト関係マッピング)の略 - オブジェクト指向と関係データベースの考え方を上手く変換して繋いでくれるもの - RDBにアクセスする際に、SQLを書かないため、見やすい - Laravel のORM はEloquent ORM ```php // sql select * from users; // orm(SQL を意識しないですむ) User::all() ``` ## Eloquent ORM のリレーション 1対多 ![](https://i.imgur.com/yJvlAIy.png) ```php <?php namespace App\Model\User; use Jenssegers\Mongodb\Eloquent\Model; class User extends Model { public function books() { return $this->hasMany(Book::class); } ``` ```php <?php namespace App\Model\Book; use Jenssegers\Mongodb\Eloquent\Model; class Book extends Model { public function user() { return $this->belongsTo(User::class); } } ``` ```php $user = User::find(1); // user に紐づく複数book を取得 $books = $user->books $book =Book::find(1); // book に紐づくUserを取得 $user = $book->user; ``` ## Eloquent ORM のリレーション(注意) #### 1+N 問題 ```php $users = User::Limit(5)->get(); $books = $users->books foreach($users as $user){ foreach($user->books as $book){ } } ``` ``` select * from books where books.user_id = 1 select * from books where books.user_id = 2 select * from books where books.user_id = 3 select * from books where books.user_id = 4 select * from books where books.user_id = 5 ``` ```php $user = User::with('books')->find(1)->get(); ``` ``` select * from posts where posts.user_id in (1, 2, 3, 4, 5) ``` https://github.com/beyondcode/laravel-query-detector https://www.yoheim.net/blog.php?q=20181104 ## Eloquent ORM Scope #### 条件をつけて検索したい ```php class Book extends Model { public function scopeName($query,$name) { $query->where('name',$name); // Query Builder 以外返すのはNG // return $query->where('name',$name)->get(); } public function scopeDate($query,$date) { $query->whereDate('created_at', '=', $date) } public function scopeHoge($query) { $query->where() ->where() ->with() ->date(''2018-08-03') ->name('速習 Laravel 6 速習シリーズ'); } } ``` ```php $user = User::find(1); $user->books()->name('速習 Laravel 6 速習シリーズ')->get() $user->books()->name('速習 Laravel 6 速習シリーズ')->Date('2018-08-03')->get() ``` ## Validation #### 基本 Laravel の思想的には、Controllerで書くのが一般的 - Validationが通らなかったら、例外処理でSessionにエラー情報を格納 ``` php function store(Request $request) { $validatedData = $request->validate([ 'title' => 'required|unique:posts|max:255', 'body' => 'required', ]); // The blog post is valid... } ``` Controllerに書きたくない人はValidator Facade を利用する ```php use Illuminate\Support\Facades\Validator; $params = ['password' => 'hugahuga']; $validator = Validator::make($params, [ 'password' => [ 'required' ] ]); ``` 個人的には、要所要所書くのがおすすめ(コントローラー、モデル) #### 拡張 独自のバリデーションを作成する方法は3,4個ある。(ありすぎ) Rule オブジェクトをよく利用する - 書くのが楽 - クラスごとに定義できるので、便利 - 独自のメッセージなどカスタム可が楽 - construct からDIできる ```php <?php namespace App\Rules\Auth; use App\Model\Admin; use Illuminate\Contracts\Validation\Rule; use Illuminate\Support\Facades\Hash; class Password implements Rule { public function passes($attribute, $value) { // $value = password // true or false で返す return Hash::check($value, User::first()->password); } public function message() { return 'ログインに失敗しました'; } } ``` ```php $validator = Validator::make($params, [ 'password' => [ 'required', new Password() ] ]); ``` #### 注意 用意されているValidationは結構あやしい挙動があるので、実際に使うときは都度検証したほうがいいかも - 型の指定はしておいた方が無難(動作が変わる) 例 max - // md参照 ## 例外処理 例外処理とはプログラムがなんらかのエラーを出したときに、処理を中断して別の処理を行うことだ。 - どういう時につかうの? https://ja.wikipedia.org/wiki/%E4%BE%8B%E5%A4%96%E5%87%A6%E7%90%86#%E4%BE%8B%E5%A4%96%E3%81%A8%E3%82%A8%E3%83%A9%E3%83%BC%E3%81%AE%E9%81%95%E3%81%84 基本的に想定外のことを例外処理とするのが鉄則らしい - 失敗した時に実行したい処理がある - 複数テーブル更新時にエラー ## Laravel 例外処理 すでにフレームワークで例外処理をある程度やってくれているので、ほぼ不要 (error 時に404 にするなど) ## よく利用する方法 メイン処理部分で都度都度、結果をチェックしている ```php $result = process1() if($result = 'error'){ return 'errorだよ' } $result = process2() if($result = 'error'){ return 'errorだよ' } $result = process3() if($result = 'error'){ return 'errorだよ' } $result = process4() if($result = 'error'){ return 'errorだよ' } ``` こっちのほうがきれい ```php process1($params) process2($params) process3($parama) process4($params) ``` ## 独自例外の作り方 #### Laravel のベース(共通処理) - report でログ残しやSlack通知 - render でレスポンスを作成 render はLaravel独自のものがあるので手はあんまり加えない ```php <?php namespace App\Exceptions; use Exception; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; class Handler extends ExceptionHandler { public function report(Exception $exception) { //$message = $exception->getMessage(); //$code = $exception->getCode(); //$file = $exception->getFile(); //$line = $exception->getLine(); //$trace = $exception->getTraceAsString(); //$params = $request->input(); parent::report($exception); } public function render($request, Exception $exception) { return parent::render($request, $exception); } } ``` #### 独自拡張 ```php <?php namespace App\Exceptions; use Exception; use Illuminate\Support\Facades\Lang; use Illuminate\Support\Facades\Log; class ApiException extends Exception { public $errors; public function __construct($errors) { $this->errors = $errros; parent::__construct(Lang::get('exception.externalApi'), 503); } /** * ここでログの記録 * * @return void */ public function report() { // slack 通知やログの記録 } /** * Render the exception into an HTTP response. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function render($request) { return response([ 'message' => '外部システムの通信エラーが発生しています', 'status' => 'error', 'errors' => $this->errors ], 503); } } ``` ```php public function mail($shopNumber) { $url = Helper::config('api.mail'); $reponse = Helper::httpPost($url); if ($reponse['status'] != 'success') { throw new ApiException($reponse['message']); } return $reponse; } ``` API呼び出しとValidation時にエラーが発生した場合は大抵共通のレスポンスなので、 例外処理を使ったほうが記述が簡単になるのではと考えています ## DI(Dependency injection) - 日本語でいう外部注入 - 委譲や合成の考え方 例:お金を支払うクラスを考える ほしい部品 お金、利用者、払い先、払い方法、、、 ```php class Money { private $money; public function __construct($money) { $this->money = $money; } } class User { private $name; public function __construct($name) { $this->name = $name; } } } class Payment { private $pay; public function __construct($pay) { $this->pay = $pay; } } ``` ```php class PaymentMoney { private user; private money; private payment; public function __construct() { $money = new Money('1500') $user = new User('saito') $payment = new Payment('paypay') } } // 上の方法だとMoenyやUserのコンストラクタが変わったら、PaymentMoenyでも変更が必要になる。 class PaymentMoney { private user; private money; private payment; public function __construct($user,$money,$payment) { $this->money = $money $this->user = $user; $this->payment = $payment; } } ``` ```php $payment = new PaymentMoney(new User('saito'),new Money('1500'),new Payment('paypay')) ``` 確かに、柔軟に対応できるけど、毎回↑のようにコンストラクタを書くのはめんどくさい Laravel では単純なクラス生成(引数なし)であれば、これを一行でかける(引数の順番も考えなくていい) ```php $payment = app(Payment::class) ``` 例 ```php class PaymentMoney { private user; private money; private payment; private validator; public function __construct($user,$money,$payment,$validator) { $this->money = $money $this->user = $user; $this->payment = $payment; $this->validator = $validator } public function pay ($params){ if(!$this->validator->validate($params)){ throw new PaymentException($this->validator->errors) } // 会計処理 $this->user->find() $this->money->add() $this->payment->pay() } } ``` 最近、Goを勉強しているのですが、継承よりもInterfaceで定義すると柔軟にできるのかも。。。と考えています。 (中身のクラスがなんであれ、メソッドを実装していればよいため) ## Kernel コントローラーでの処理前、後で共通の処理をしている #### Laravel での処理 ```php <?php namespace App\Http; use Illuminate\Foundation\Http\Kernel as HttpKernel; class Kernel extends HttpKernel { protected $middleware = [ \App\Http\Middleware\TrustProxies::class, \App\Http\Middleware\CheckForMaintenanceMode::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\TrimStrings::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, ]; protected $middlewareGroups = [ 'web' => [ \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, // \Illuminate\Session\Middleware\AuthenticateSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, ], 'api' => [ 'throttle:60,1', \Illuminate\Routing\Middleware\SubstituteBindings::class, \App\Http\Middleware\RequireJson::class, // 追加 ], ]; protected $routeMiddleware = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, // original 'CheckLogin' => \App\Http\Middleware\CheckLogin::class, ]; } ``` #### 独自のミドルウェア定義 ```php <?php namespace App\Http\Middleware; use Closure; class CheckLogin { /** * ログインチェック * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { if (!session('login')) { return redirect()->route('login.create'); } return $next($request); } } ``` #### ミドルウェアの利用 web.php,api.phpなどに記載 ```php <?php // 認証必要 Route::group(["middleware" => 'CheckLogin'], function () { Route::group(['namespace' => 'Hoge'], function () { Route::get('hoge', ['uses' => 'HogeController@index', 'as' => 'hoge.index']); }); }); // 認証不要 Route::group(['namespace' => 'Session'], function () { Route::get('login', ['uses' => 'LoginController@create', 'as' => 'login.create']); }); // どのルートにもマッチしない Route::fallback(function () { return response()->view('errors.404'); })->name('fallback.404'); ``` ## Laravel を使った感想 - 使い方、実装方法が定義されていないので、色々なやり方がある - Rails などに比べると非常に柔軟(Railsは決まったやり方から拡張させるのは結構しんどい) - 多くのシステムで利用されている(きっと)のでオンラインでのナレッジが豊富 - 簡素なシステムであれば、すぐに実装できる - 最近だと、LaravelMix など、WebPackを簡易的にかけるシステムなどある - Oauthや様々なサービスが簡単に実装できる