- 投稿日:2019-12-15T23:12:37+09:00
DBとLaravelの接続設定【備忘録】
はじめに
PHP/Laravelの勉強にて備忘録として
前提
MySQLがインストールされていること
LaravelのDB接続設定ファイル
config/database.php
を開くconfig/database.php/*中略*/ 'default' => env('DB_CONNECTION', 'mysql'), /*中略*/となっている。
env('DB_CONNECTION', 'mysql')
上記のenvはDB_CONNECTIONが設定されていればDB_CONNECTION
設定されていなければMySQLを使用という意味であるここでLaravelの環境変数を見てみる。
tinker(php artisan tinker)を起動し、
print_r($_ENV)
を入力$ php artisan tinker $ print_r($_ENV) <中略> [APP_DEBUG] => true [APP_LOG_LEVEL] => debug [APP_URL] => http://localhost [DB_CONNECTION] => mysql [DB_HOST] => 127.0.0.1 [DB_PORT] => 3306 <中略>DB_CONNECTIONはmysqlになっていることを確認。
ではLaravelの環境変数を決めているのは誰か。プロジェクトトップにある.envファイルを開く
.env<中略> APP_URL=http://localhost DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 <中略>これの
DB_CONNECTION=mysql
となっているところを使用したいDBに指定する。
今回はMySQLを使用するのでそのままconfig/database.php/*中略*/ 'mysql' => [ 'driver' => 'mysql', 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'strict' => true, 'engine' => null, ], /*中略*/
config/database.php
で自分が使用するmysql(今回は)の詳細設定をする重要なのはdatabase, username, passwordで、すべenvなので.envで編集する
.env<中略> DB_DATABASE=homestead DB_USERNAME=homestead DB_PASSWORD=secret <中略>今のままではDB名、ユーザ名、パスワードが上記のままなので変える。
.env<中略> DB_DATABASE=test DB_USERNAME=root DB_PASSWORD= <中略>上記のようにするとDB名がtest ユーザ名がrootでパスワード無しでログインする。
testデータベースを作成する
$ sudo service mysqld start $ mysql -u root mysql> CREATE DATABASE `test`;データベースが接続されているか確認
$ php artisan tinker >>> DB::connection(); => Illuminate\Database\MySqlConnection {#2891}タイムゾーン設定
タイムゾーンの設定を「東京(Asia/Tokyo)」にする
レコード保存時に時間情報が設定したタイムゾーンの時間で保存されるconfig/app.php'timezone' => 'Asia/Tokyo',以上
- 投稿日:2019-12-15T22:56:47+09:00
Laravelで複数の宛先にメールを送信するときにつまづいた。
環境
Laravel 5.5
背景
仕事で、とあるアクションを行った時に複数の宛先にメールを送る方法がわからずに四苦八苦してしまった。
問題点
app/Mail/sendmail.phppublic function build() { return $this->from('XXXXfromXXXX@gmail.com') ->subject('テスト送信完了') ->to(????) ->view('emails.sendmail'); }初めは直接->to()の中に記述していたが、エラーでうまくいかず...
見るべき場所
途中Maiableを継承している場所を確認すればいいことに気づいた。
app/Mail/sendmail.phpuse Illuminate\Mail\Mailable; class OrderShipped extends Mailable {illuminateなのでvendor以下のMailableを確認する。
vendor\laravel\framework\src\Illuminate\Mail\Mailable.php/** * The "to" recipients of the message. * * @var array */ public $to = [];変数$toは配列で初期化されていることを発見。
(中の処理を詳しく見たい方はvendor以下のMailableクラスを参考にしてください。)問題解決
配列を作成して、引数として渡す方法で解決した。
app/Mail/sendmail.phppublic function build() { $to = array('XXXto1XXX@gmail.com','XXXto2XXX@yahoo.co.jp'); return $this->from('XXXXfromXXXX@gmail.com') ->subject('テスト送信完了') ->to($to) ->view('emails.sendmail'); }気づき
継承しているクラスを確認するのが大事。
- 投稿日:2019-12-15T21:47:53+09:00
AmazonインセンティブAPI実装(Amazonギフト券)
経緯
Amazonギフトカード実装をお願いされ、気軽にokを出したものの、概要がとってもわかりずらく、かつ参考文献が全くなく、意外と四苦八苦したので、同じ悩みの方が出ないように投稿する事にしました。
なお、事前に用意されたphpのサンプルコードからLaravelに実装し直す、という流れが一番良いと思いますが、あまりにも面倒だったのと時間との勝負だった為、決してベストプラクティスでないことはご了承ください。。本題
登録→確認方法
基本的には導入マニュアルがあるので、これに沿って開発をしていきます。
AmazonインセンティブAPIとの統合プロセスについて
登録した後、サンドボックス環境で正常に動作するか確認サンプルコード参照
FAQ付録にサンドボックス環境のサンプルコードがあります。
今回はphp/Laravelで実装予定だったので、ここのphpのサンプルコードを読み、htmlSDKv2_php_cr/htmlSDKv2_php/rollups/function.php
にキー発行実装があります。実装方法
- fuciton.phpのみ取り出し、app/Library配下に設置
- controllerからのinvokeRequest を指定
- Laravel実装の為、globalで引っ張ってきてるところをconfigにまとめ、アクセスキー等は.envに設定
- 実装完了。。。。
注意点としては、サンドボックス環境とプロダクション環境の2つともアクセスキーを発行しなければいけません。
余談(メイン)
今回は、jsonで返したので、XML2Array.phpは使用していません。
今回の僕の実装の流れとしては、
登録した後、サンドボックス環境でidが合っているか確認するところまでは順調でした。
ただresponseを叩いた時に、雲行きが怪しくなりました。responseの情報以外に沢山の情報があり、気づいてしまったんです。あれ、、???これ、、、、api叩くだけじゃダメじゃね、、、?
そうです。僕は必要なrequestを提げて、apiを叩けばそれで実装が完了すると思っていました。
そんな甘い世界ではありません。このapiではSignatureキーを採用しているので、あちらで発行するキーと同じ発行をしなければなりませんでした。
その実装が開発マニュアルに書いてあるのかと思いきや、そのような実装は見つからずましてや必要なrequestすら曖昧すぎてよくわからなかったです。泣きそうでした。そんな泣きそうな中、FAQ付録にサンドボックス環境においてのサンプルコードを見つけ、phpのサンプルコードの
htmlSDKv2_php_cr/htmlSDKv2_php/rollups/function.php
にたどり着きました。。実装する人は少ないと思いますが、今後同じように実装する人を救えますように。。
- 投稿日:2019-12-15T21:30:16+09:00
2019年の勉強会を集計してみた〜都道府県別&キーワード別〜
この記事はAteam Hikkoshi Samurai Inc. & Ateam Connect Inc.(エイチーム引越し侍、エイチームコネクト) Advent Calendar 2019 15日目の記事になります。
はじめに
皆さん勉強会はお好きでしょうか?
私は興味のあるものを見つけては社内外の開催に関わらず参加しています
それまで知らなかった技術や知見に出会えることがその醍醐味かなと思っています?さて、2019年はどのような勉強会がどこで多く開催されたのでしょうか?
個人的に気になって集計してみたので結果とそのやり方を公開します!結果
2019年に開催された勉強会(イベント)の集計結果です。
都道府県別開催数
まずは都道府県別の開催数です。
東京の開催数が多すぎて単純な数値にすると他県の棒が潰れてしまうため対数軸にしています。
さすが東京!割合で見てもやはり東京が際立ちます。
上位10県の数値は以下の通りです。
都道府県 開催数 東京都 23486 大阪府 4360 愛知県 1812 福岡県 1294 神奈川県 1012 兵庫県 951 京都府 681 北海道 678 沖縄県 395 広島県 291 ソフトウェア業,情報処理・提供サービス業,インターネット附随サービス業の事業従事者数との割合
都道府県別のエンジニア1人あたりの勉強会開催数を出そうと思いました。
しかし、都道府県別のエンジニア人口のデータが見つからなかったため、「平成 30 年特定サービス産業実態調査報告書(経済産業省)」から以下の業種の県別事業従事者数を合計した値を参考値として使いました。
なお、令和元年の数値は本記事執筆時点では公開されていません。
- ソフトウェア業
- 日本標準産業分類に掲げる小分類 391-ソフトウェア業に属する業務を主業として営む 事業所
- 情報処理・提供サービス業
- 日本標準産業分類に掲げる小分類 392-情報処理・提供サービス業に属する業務を主 業として営む事業所
- インターネット附随サービス業
- 日本標準産業分類に掲げる小分類 401-インターネット附随サービス業に属する業務を 主業として営む事業所
結果は以下の通りです。
割合 = 開催数 / (上記3業種の事業従業者数の合計)
です。奈良の数値が高いのは少し意外でした。島根はRubyゆかりの地であることが影響しているのかもしれません。
タグ(キーワード)別開催数
勉強会のタイトルや内容説明を基にタグ付けを行い集計しました。
なお、1つの勉強会に対して複数のタグがつくことがあります。
勉強会
、プログラミング
、初心者
、Web
、入門
、初心者向け
などが多くの割合を締めていますが、もう少し具体的な技術の傾向を見たかったので、これらを除外して再度集計しました。これを見るとAIやIoTなどの近年のITトレンドが反映されていると感じます。
都道府県別タグ上位
東京、大阪、愛知でタグの多いものを見てみましょう。
なお、勉強会
、プログラミング
、初心者
、Web
、入門
、初心者向け
は除外しています。東京
大阪
愛知
例えば愛知だけは
PHP
が4位に入っているなど、4位以下で少しづつ地域の違いが出ているように見えます。注意事項
以上の結果について、後述の集計方法に記載の通り厳密に勉強会だけを集計していない点はご了承ください。
集計方法
ここからはどうやって集計したかを説明します。
情報源
勉強会の情報は以下のサービスが提供しているAPIから取得しています。
今回の集計では勉強会=これらのサイトで登録されたイベントとしています。
厳密には懇親会などの勉強会でないイベントも含まれています。
また、ITに関係ないものも存在しますが、今回の集計では除外していません?タグの一覧はQiitaのAPIから取得しています
今回はQiitaの記事数が多いタグ順に300件を取得するようにしました。方法
データベースにイベント情報を保存し、Metabaseで集計をしています。
テーブルは以下の通りです。
- prefectures:都道県マスタ
- events:勉強会(イベント)
- tags:タグマスタ
- event_tag:勉強会とタグの多対多関係を表現するための中間テーブル
処理
勉強会の情報をテーブルに保存するために以下の処理をしています。
- 都道府県マスタのデータ登録
- タグ一覧を取得
- 勉強会情報を取得
- 勉強会がどの都道府県で開催されているか判定する
- 勉強会にタグ付けをする
今回は現在絶賛学習中のLaravelで作ってみました。
以下からはコードの話が中心になるので、興味のある方は読んでいただけると幸いです。都道府県マスタのデータ登録
都道府県データはlaravelのseederで初期登録できるようにしました。
# seederファイルの作成 $php artisan make:seeder PrefectureSeederseederの内容です。
なお、prefectures.id
はJISで規定されている都道府県コードの値にしています。<?php use Illuminate\Database\Seeder; use App\Models\Prefecture; class PrefectureSeeder extends Seeder { public function run() { $prefectures = [ [1, '北海道', 'ホッカイドウ', 'hokkaido'], [2, '青森県', 'アオモリケン', 'aomori'], [3, '岩手県', 'イワテケン', 'iwate'], //省略 [45, '宮崎県', 'ミヤザキケン', 'miyazaki'], [46, '鹿児島県', 'カゴシマケン', 'kagoshima'], [47, '沖縄県', 'オキナワケン', 'okinawa'] ]; foreach ($prefectures as $prefecture) { Prefecture::updateOrCreate([ 'id' => $prefecture[0], 'name' => $prefecture[1], 'kana_name' => $prefecture[2], 'english_name' => $prefecture[3] ]); } } }これで
$php artisan db:seed --class PrefectureSeeder
を実行するとデータが登録されるようになります。タグ一覧を取得
コマンドでQiitaのAPIからタグを取得するようにします。
$ php artisan make:command FetchQiitaTagsFromApi
を実行するとコマンドの雛形となるファイルが作成されます。<?php namespace App\Console\Commands; use App\Services\QiitaService; use Illuminate\Console\Command; use Exception; class FetchQiitaTagsFromApi extends Command { protected $signature = 'fetch:tags';//コマンド名 protected $description = 'fetch Qiita tags from api';//説明 public function __construct() { parent::__construct(); } public function handle() { try { QiitaService::fetchTagsFromApi(1, 3); } catch (Exception $e) { $this->error($e->getMessage()); } } }QiitaServiceではAPIで取得した結果を、DBに保存しています。
なお、$jsonArray = ApiClient::getJsonArray($url, $params, 60000);
の60000はAPIアクセスのディレイ時間(ms)です。
値はAPIの利用規約やrobots.txtに従って設定します。<?php namespace App\Services; use App\Repositories\TagRepository; use App\ApiClients\ApiClient; class QiitaService { const QIITA_HOST = 'https://qiita.com'; const API_PATHS = ['tags' => '/api/v2/tags',]; public static function fetchTagsFromApi(int $startPage = 1, int $endPage = 10) { $url = self::QIITA_HOST . self::API_PATHS['tags']; $params = ['per_page' => '100', 'sort' => 'count']; $tagRepository = app(TagRepository::class); for ($i = $startPage; $i <= $endPage; $i++) { $params['page'] = $i; $jsonArray = ApiClient::getJsonArray($url, $params, 60000); $tagNames = array_column($jsonArray, 'id'); $tagRepository->saveTagsFromNames($tagNames); } } }ApiClientはAPIアクセスを実行するクラスです。
HTTPクライアントのライブラリGuzzleを使用しています。
後述するイベント情報の取得でもこのクラスを使用しています。<?php namespace App\ApiClients; use GuzzleHttp\Client; use GuzzleHttp\Exception\ClientException; class ApiClient { public static function getJsonArray(string $url, array $queryParams, float $delay = 0.0, array $headers = []) { try { $client = new Client(); $res = $client->request('GET', $url, ['query' => $queryParams, 'delay' => $delay, 'headers' => $headers]); return json_decode($res->getBody(), true); } catch (ClientException $e) { throw $e; } } }勉強会情報を取得
やっていることはタグ一覧を取得でやっていることとほぼ同じです。(APIから情報取得->DBに保存)
ここではDoorkeeperのイベント取得処理を例に出していますが、他のサイトの場合も行っていることは基本的に同じです。<?php namespace App\Console\Commands; use App\Services\DoorkeeperService; use Carbon\Carbon; use Illuminate\Console\Command; class FetchDoorKeeperEventsFromAPI extends Command { protected $signature = 'fetch:doorkeeper {--p|page= : the page offset of the results } {--l|locale=ja : the localized text for an event } {--o|sort=starts_at : The order of the results(One of published_at, starts_at, updated_at) } {--s|since=-1month : Only events taking place during or after this date will be included (YYYYMMDD) } {--u|until= : Only events taking place during or before this date will be included (YYYYMMDD) } {--k|keyword= : Keyword to search for from the title or description fields } {--g|expand_group=0 : Expands the group object(0:false, 1:true) } {--a|all=1 : paging to get all records(0:false, 1:true) }'; protected $description = 'fetch doorkeeper events from api and store to DB'; public function __construct() { parent::__construct(); } public function handle() { $queryParams = $this->getQueryParamFromOptions(); DoorkeeperService::fetchEventsFromAPI($queryParams, !!$this->option('all')); } private function getQueryParamFromOptions() { $options = $this->options(); //除外オプション $excludeOptions = ['expand_group', 'all']; foreach ($excludeOptions as $excludeOption) { unset($options[$excludeOption]); } //オプションとパラメータの対応 $optionToParam = [ 'page' => 'page', 'locale' => 'locale', 'sort' => 'sort', 'since' => 'since', 'until' => 'until', 'keyword' => 'q', 'expand_group' => 'expand[]', ]; $params = []; foreach ($options as $option => $value) { if ($value) { $params[$optionToParam[$option]] = $value; } } if ($options['since']) { $jst = new Carbon($options['since']); $params['since'] = $jst->setTimezone('UTC')->format('Y-m-d\TH:i:s\Z'); } if ($options['until']) { $jst = new Carbon($options['until']); $params['until'] = $jst->setTimezone('UTC')->format('Y-m-d\TH:i:s\Z'); } if ($this->option('expand_group')) { $params[$optionToParam['expand_group']] = 'group'; } return $params; } }
DoorkeeperService
ではDoorkeeperのイベント情報の取得と保存を行います。
APIから返される情報に都道府県だけの情報は存在しないので、取得する際にPrefectureService
で判定をしています。<?php namespace App\Services; use App\Models\Event; use Carbon\Carbon; use App\ApiClients\ApiClient; class DoorkeeperService { private const EVENT_API_URL = 'https://api.doorkeeper.jp/events'; static public function fetchEventsFromAPI(array $params, bool $isAllPage) { if ($isAllPage) { for ($p = 1; true; $p++) { $params['page'] = $p; $headers = ['Authorization' => 'Bearer ' . config('env.doorkeeper_api_token')]; $jsonArray = ApiClient::getJsonArray(self::EVENT_API_URL, $params, 5000.0, $headers); if (empty($jsonArray)) { break; } self::updateOCreateEventsFromAPIResult($jsonArray); } } else { $jsonArray = ApiClient::getJsonArray(self::EVENT_API_URL, $params, 5000.0); self::updateOCreateEventsFromAPIResult($jsonArray); } } static private function updateOCreateEventsFromAPIResult(array $eventJson) { foreach ($eventJson as $record) { $event = $record['event']; //住所、または緯度経度から都道府県を判定 $prefecture = $event['address'] ? PrefectureService::getPrefectureFromAddress($event['address']) : null; if (!$prefecture && $event['lat'] && $event['long']) { $prefecture = PrefectureService::getPrefectureFromCoordinates($event['lat'], $event['long']); } Event::updateOrCreate( [ 'site_name' => Event::DOORKEEPER, 'event_id' => $event['id'] ], [ 'title' => $event['title'], 'description' => $event['description'], 'event_url' => $event['public_url'], 'prefecture_id' => $prefecture ? $prefecture->id : null, 'address' => $event['address'], 'place' => $event['venue_name'], 'lat' => $event['lat'], 'lon' => $event['long'], 'started_at' => self::formatUtcTimeToMySqlDateTime($event['starts_at']), 'ended_at' => self::formatUtcTimeToMySqlDateTime($event['ends_at']), 'limit' => $event['ticket_limit'], 'participants' => $event['participants'], 'waiting' => $event['waitlisted'], 'group_id' => $event['group'], 'event_created_at' => self::formatUtcTimeToMySqlDateTime($event['published_at']), 'event_updated_at' => self::formatUtcTimeToMySqlDateTime($event['updated_at']), ] ); } } static private function formatUtcTimeToMySqlDateTime(string $date) { $carbon = new Carbon($date); return $carbon->setTimezone('JST')->format('Y-m-d H:i:s'); } }勉強会がどの都道府県で開催されているか判定する
getPrefectureFromAddress
は住所から都道府県を判定する処理に使います。
getPrefectureFromCoordinates
はHeartRails Geo APIで緯度経度から都道府県を判定する処理に使います。<?php namespace App\Services; use App\Models\Prefecture; use App\ApiClients\ApiClient; class PrefectureService { public static function getPrefectureFromAddress(string $address = ''): ?Prefecture { $prefectures = Prefecture::select(['id', 'name'])->get(); foreach ($prefectures as $prefecture) { if (mb_strpos($address, $prefecture->name) !== false) { return $prefecture; } } return null; } public static function getPrefectureFromCoordinates(float $lat, float $lon): ?Prefecture { $url = config('const.geoapi_url'); $params = [ 'method' => 'searchByGeoLocation', 'x' => $lon, 'y' => $lat, ]; $jsonArray = ApiClient::getJsonArray($url, $params, 5000.0); if (isset($jsonArray['response']['error'])) { return null; } //1件目のデータの都道府県名 $firstPrefectureName = $jsonArray['response']['location'][0]['prefecture']; $prefecture = Prefecture::where('name', $firstPrefectureName)->first(); return $prefecture; } }ただ、これらの処理で判定できないもの(開催住所が市や区ではじまり、緯度経度情報もない)は手動でデータを整えました^^;
政令指定都市や東京都の区くらいはマスタ化して判定を自動化してもよかったかもと思いました。
また、判定できなかったものの中にはそもそもZoomなどで完全にオンラインで開催されている場合がありました。
将来的にはどこに住んでいる人でも参加しやすいオンライン形式が増えていくのかもしれません。勉強会にタグ付けをする
APIで取得できるイベント情報にタグ情報はないため自前で付与します。
イベント情報にはタイトル、キャッチ、説明があり、これらの中にタグに該当するキーワードがあるかで判定しています。
ただし、例えばR
やGo
の場合、単純にこれらの文字列があるかで判定すると誤ってタグ付けされる可能性が高いため正規表現で検索するようにします。Go
の場合は/golang|go言語/ui
としました。
他にもAI
は/AI/u
として小文字を含まないようにするなどキーワードごとに調整しています。この正規表現はDBの
tags.pattern
に保存しています(ここは手作業^^;)
また、説明は全部でなく最初の200文字のみを検索対象にしています。<?php namespace App\Console\Commands; use App\Models\Tag; use Illuminate\Console\Command; use Illuminate\Support\Facades\Config; use App\Models\Event; use Illuminate\Support\Facades\DB; class TaggingForEvents extends Command { protected $signature = 'tagging:events'; protected $description = 'tagging for events'; public function __construct() { parent::__construct(); } public function handle() { $characterLimit = 200; //descriptionのタグの探索範囲 $events = Event::all(); $tags = DB::table('tags')->orderBy('id')->limit(400)->get(); foreach ($events as $event) { $tagIds = []; foreach ($tags as $tag) { $pattern = $tag->pattern; $description = mb_substr(strip_tags($event->description), 0, $characterLimit); if (preg_match($pattern, $event->title) || preg_match($pattern, $event->catch) || preg_match($pattern, $description)) { $tagIds[] = $tag->id; } } $event->tags()->sync($tagIds); } } }Metabaseでの集計
集計結果でお見せしたグラフはMetabaseで作成しています。
dockerが使える環境であれば、以下のコマンドで簡単にMetabaseを実行することができます。
ただし、このやり方の場合はコンテナを止めるとMetabaseで保存した集計は全て消えるのでご注意ください。$ docker run -d -p 12345:3000 --name metabase metabase/metabase後はブラウザから
localhost:12345
にアクセスすれば、Metabaseの画面が表示されます。
画面上でユーザ登録と接続先DBの情報を入力してやればすぐに使い始めることができます。
ちなみにコンテナからlocalホストへ接続する場合、macであればdocker.for.mac.localhost
をwindowsであればdocker.for.win.localhost
をホスト名に指定します。まとめ
- ATND、connpass、DoorKeeperから勉強会(イベント)の情報を取得し集計した。
- イベントに対して、Qiitaのタグでタグ付けした。
- 東京、大阪、愛知が都道府県別のイベント開催数のトップ3
- 全国的にAI(Python、機械学習、AIなど)に関連するイベントが多く開催された。
お知らせ
エイチームグループでは一緒に活躍してくれる優秀な人材を募集中です。
興味のある方はぜひともエイチームグループ採用ページよりご応募ください!Qiita Jobsのエイチーム引越し侍社内システム企画 / 開発チーム、社内システム開発エンジニアを募集!からチャットでご質問いただくことも可能です!
明日
明日は後輩の面倒見の良さが評判の @umesour さんの記事です!
どんな記事を書いてくださるのかとても楽しみです!!
乞うご期待!!
- 投稿日:2019-12-15T20:52:18+09:00
Laravelで.envに問題がある場合はデプロイ時にエラーにする
前置き
今時のWebアプリケーション開発では、開発環境と本番環境はほとんど同じ構成になっている上、VCSで一元管理されているソースコードを自動デプロイツールでデプロイするので、デプロイ時に問題が起こることはなかなかありません。
ですが中にはどうしても開発環境と本番環境で同等にできず、その上VCSで管理することもできず、デプロイ時に問題を起こしがちなものもあります。
Laravelにおいては
.env
がそれです。このファイルは環境ごとに変わるデータを入れるのが目的のため開発環境と本番環境で同じものを使うことは基本的にできない上、APIのキー等の秘密情報を入れることも多く、VCSで管理することもできません。.env# .envの一部。データベースの接続情報などが記載されている。 DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=laravel DB_USERNAME=root DB_PASSWORD=実際、
laravel new
して自動生成される.gitignore
ではこのファイルが除外リストに入っています。一度用意した後は頻繁に変更するものではありませんが、それでもたまにこのファイルに入れておきたいデータが増えることがあります。
そのような変更の後には
.env
を手動で変更した上でデプロイする必要があります。.env
の変更を忘れると、場合によっては致命的な問題になります。外部の決済システムを変更した後、新しい決済システム用の認証情報がない場合、どうなるでしょう?本題
今日はLaravel Env ValidatorとLaravel Deployerを使ってこのような問題をできる限り防ぐための方法をご紹介します。
Laravel Env Validatorは、
.env
(というか環境変数自体)をLaravelのヴァリデーションルールを使ってヴァリデーションする機能を提供するパッケージです。
config/laravel-env-validator.php
に以下のように記述して、config/laravel-env-validator.php<?php return [ 'rules' => [ 'APP_ENV' => 'in:local,production', 'APP_KEY' => 'required', 'APP_DEBUG' => 'boolean', ], ];
php artisan config:env-validator
と実行すると、環境変数が検証され、たとえば上記の場合だと、APP_ENV
がtestingだったり、APP_KEY
が未設定の状態だったりすると以下のようなエラーを出します。In EnvValidator.php line 31: The .env file has some problems. Please check config/laravel-env-validator.php The selected APP_ENV is invalid. The APP_KEY field is required.注意としてはconfigとして記述するため、
Illuminate\Validation\Rule
のルールを使うようなことはできません。というか実際にはできないことはないのですが、php artisan config:cache
でエラーになってしまうため実用的ではありません。シリアライズできないんですね。ですので複雑なルールを書くような場合は、カスタムルールを用意するのがよさそうです。さて、これだけでも便利ですが、できればデプロイ時に自動でヴァリデーションさせたいところです。自動で防げるミスは防ぎたいですよね。
そこでLaravel Deployerで以下のように設定します。
まず、
php artisan config:env-validator
を実行するタスクをレシピに書きます。recipe/artisan-config-env-validator.php<?php namespace Deployer; desc('validate .env'); task('artisan:config:env-validator', artisan('config:env-validator'));次にこのレシピをincludeします。
config/deploy.php
は、php artisan deploy:init
で事前に用意しておいてください。config/deploy.php'include' => [ 'recipe/artisan-config-env-validator.php', ],最後に、このタスクを実行するよう設定します。
config/deploy.php'ready' => [ 'artisan:storage:link', 'artisan:view:clear', 'artisan:cache:clear', 'artisan:config:env-validator', 'artisan:config:cache', 'artisan:migrate', ],
artisan:config:cache
の前に実行するのが肝です。config:cacheされた後は環境変数は読み込まれないため、環境変数を直接見ているLaravel Env Validatorのヴァリデーションは正常に動作しません。後はその他必要なデプロイのための設定をして、デプロイするだけです。.envをヴァリデーションして問題なければデプロイが成功しますし、そうでなければ失敗します。これにより、適切なルールを用意している限り、.envの記述ミスで問題のあるデプロイをしてしまうことがなくなります。
- 投稿日:2019-12-15T19:15:14+09:00
Laravel構築
Apacheインストール
# yum -y install httpd (yオプションは問い合わせに対して全て「y」で応答) 以下のコマンドで確認 # httpd -v Server version: Apache/2.4.6 (CentOS) Server built: Aug 8 2019 11:41:18起動確認
# systemctl status httpd ● httpd.service - The Apache HTTP Server Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; vendor preset: disabled) Active: inactive (dead) Docs: man:httpd(8) man:apachectl(8)
Active: inactive (dead)
は停止Apache起動
# systemctl start httpdPHP インストール
- PHP5.4
CentOS7の標準リポジトリのPHPバージョンが5.4
(古い...サポート切れてるやろ)
PHP5.4以降をインストールする場合は、『PHP7.3』を参照(今回は7.3を例に)# yum -y install php 以下のコマンド確認 # php -v PHP 5.4.16 (cli) (built: Oct 30 2018 19:30:51) Copyright (c) 1997-2013 The PHP Group Zend Engine v2.4.0, Copyright (c) 1998-2013 Zend Technologies
- PHP7.3
EPELリポジトリをインストール
# yum -y install epel-releaseREMIリポジトリをインストール(リポジトリの依存関係によりEPELリポジトリも同時にインストールされる)
# yum -y install http://rpms.famillecollet.com/enterprise/remi-release-7.rpmPHP7.3をインストール
# yum -y install --enablerepo=remi,remi-php73 php php-mbstring php-xml php-xmlrpc php-gd php-pdo php-pecl-mcrypt php-mysqlnd php-pecl-mysql php-zip php-pecl-zip zip unzip git 以下のコマンドで確認 # php -v PHP 7.3.11 (cli) (built: Oct 22 2019 08:11:04) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.3.11, Copyright (c) 1998-2018 Zend TechnologiesCentOS7からMariaDBが標準となっているため、
MySQLをインストールするためには削除する必要がある。# yum remove mariadb-libs # rm -rf /var/lib/mysqlMySQLのリポジトリをインストール
# yum localinstall -y https://dev.mysql.com/get/mysql80-community-release-el7-2.noarch.rpmMySQLインストール
# yum install -y mysql-community-server 以下のコマンドで確認 # mysqld --version /usr/sbin/mysqld Ver 8.0.18 for Linux on x86_64 (MySQL Community Server - GPL)MySQLの状態を確認(デフォルトでは停止のはず)
# systemctl status mysqld ● mysqld.service - MySQL Server Loaded: loaded (/usr/lib/systemd/system/mysqld.service; enabled; vendor preset: disabled) Active: inactive (dead) Docs: man:mysqld(8) http://dev.mysql.com/doc/refman/en/using-systemd.html
Active: inactive (dead)
は停止MySQLを起動
# systemctl start mysqld 以下のコマンドで確認 # systemctl status mysqld ● mysqld.service - MySQL Server Loaded: loaded (/usr/lib/systemd/system/mysqld.service; enabled; vendor preset: disabled) Active: active (running) since Mon 2019-11-04 00:23:13 JST; 8s ago Docs: man:mysqld(8) http://dev.mysql.com/doc/refman/en/using-systemd.html Process: 5342 ExecStartPre=/usr/bin/mysqld_pre_systemd (code=exited, status=0/SUCCESS) Main PID: 5417 (mysqld) Status: "Server is operational" CGroup: /system.slice/mysqld.service └─5417 /usr/sbin/mysqld Nov 04 00:22:59 localhost.localdomain systemd[1]: Starting MySQL Server... Nov 04 00:23:13 localhost.localdomain systemd[1]: Started MySQL Server.
Active: active (running)
で起動を確認MySQLの自動起動設定を確認する(デフォルトは有効のはず)
# systemctl is-enabled mysqld enabled
enable
なので有効変更は以下のコマンドで行う
無効にする # systemctl disable mysqld Removed symlink /etc/systemd/system/multi-user.target.wants/mysqld.service. 有効にする # systemctl enable mysqld Created symlink from /etc/systemd/system/multi-user.target.wants/mysqld.service to /usr/lib/systemd/system/mysqld.service.composerをインストール
# curl -sS https://getcomposer.org/installer | php コマンド設置 # mv composer.phar /usr/local/bin/composer 以下のコマンドで確認 # composer -v ______ / ____/___ ____ ___ ____ ____ ________ _____ / / / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/ / /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ / \____/\____/_/ /_/ /_/ .___/\____/____/\___/_/ /_/ Composer version 1.9.1 2019-11-01 17:20:17Laravelのインストーラーをダウンロード
# composer global require "laravel/installer"プロジェクトを配置する場所のディレクトリのパーミッションを変更
(一旦、実行・書込・読込を許可)# chmod 777 /var/www/html/"sample"というプロジェクトを作成
$ composer create-project --prefer-dist laravel/laravel /var/www/html/sampleドキュメントルート設定
# vi /etc/httpd/conf.d/***.conf(任意の名前) <VirtualHost *:80> DocumentRoot /var/www/html/sample/public RewriteEngine On <Directory "/var/www/html/sample/public"> AllowOverride All </Directory> </VirtualHost>confを修正したのでhttpd再起動
# systemctl restart httpdログを書込権限がないので、パーミッションを変更
(一旦、実行・書込・読込を許可)$ chmod -R 777 /var/www/html/sample/storage/http://192.168.33.10 にアクセス
(Vagrantで構築したため、デフォルトの192.168.33.10)
- 投稿日:2019-12-15T17:01:27+09:00
PHPerが久しぶりにRubyを書いて思ったこと
内容
- PHPer(Laravler)が感じたRuby(Rails)への感想
私の背景
- PHP成分が多い人生
- 初期だとFuelPHPとか触ってた
- ここ数年はLaravel使ってた
- Rubyは2011年頃に遊びで触ってた
- メタプログラミングRubyとか読んで興奮した記憶はある
- この当時のRailsを触って「ほーん、なるほど」、みたいに思った記憶もある
- 仕事では結局使わなかったけど、Project Euler をRubyで解いたりしてみてた
といった感じの背景です。
最近Railsで仕事することになったので、PHPer(というかLaraveler)が感じたRailsに対するギャップをいくつか書きたいと思います。
すでにRailsのプロジェクトがあり、そこにJOINした感じです。Autoloadがわからない
PHPのロード
require_once __DIR__ . '/path/to/file.php'Rubyのロード
require "path/to/file"ここまではなんの問題もなくわかるんですが、autoloadがわからない。
PHPの場合
namespace \Foo; use \Foo\Bar\Hoge; class Xxx extends Hoge { }みたいな感じで「利用するクラスのフルパスを書かなければならない」っていう制約があるので、面倒ではあるんだけど「
(appRoot)/Foo/Bar/Hoge.php
にあるんだな」っていうことが分かる。最近はほとんどPSR-4のautoloadが多いと思うので、PSR-4読めばいいと思う
参考: https://qiita.com/inouet/items/0208237629496070bbd4
Ruby(Rails)の場合
module Foo do class Xxx extends Hoge end endなんなん!?!?!?急に
Hoge
どこから出てきたん!?!?!?!?
Hogeって誰なの!?!?!?!ってすごく思った。
https://qiita.com/eggc/items/ae09d32df5d994522ca1
https://qiita.com/tachiba/items/5b293ca8e9430b9bd07e最新のバージョンでどう動くかはわからんですけど「Railsが頑張って探してるんや」って言われて納得。
納得すると同時に「あぁ〜〜〜パスを明示したいんじゃ〜〜〜」という気持ちになる。
カッコがない追記: 以下「カッコがない」の項については私の勘違いが多分に含まれてたのでスルーしてください。
コメントにてご指摘いただき、ありがとうございました![]()
PHPのこれがpiyo(bar(foo($x)));
Rubyだとこうなるpiyo bar foo x
いいんだけど、「どんだけカッコ嫌いなん???」って気持ちで溢れた。
foo
の引数が3つあるときはpiyo bar foo 1,2,3
ってなって、なんか、分かるんだけど、なんやねんカッコ書きたいやんって気持ちになるpiyo(bar(foo(1,2,3))
シックリくる〜〜!
って思うけどRobocopとか言うやつが「カッコ書くなや」みたいにいってくる。もうやだ。blockに感じる違和感
[1,2,3].reject {|n| n > 2}.map {|n| n * 2}.map &:to_sこれと
[1,2,3].reject do |n| n > 2 end.map do |n| n * 2 end.map &:to_sこれって同じ感じだけど、なんで構文2つ分けたん。どっちかひとつでいいじゃんね!
end.map
とかにすごくなにか感じる。returnが無い
def hoge(x) if x > 5 return x * 3 end x endreturnつけたりつけなかったり!!!なんなの!!!
不要なのにreturnつけるとRobocopが「てめぇreturnつけてんじゃねぇよ!」とか言ってくるしもう。もう。
RubyDocが無い・・・?
最近のPHPは割と型がしっかり・・・とは言わないけど、少なくとも宣言する際の不便はかなりなくなってきた。
コンパイル用っていうよりは、プログラミングするときの手助けとしての型、という側面でとても便利。
PHP7より前の時代においてもPHPはPHPDoc
で何となく「型が何なのか明示しろや」文化が多少なりとあった。class Foo { /** * @param integer $x * @param integer $y * @return integer */ public function something($x, $y) { return $x * $y; } }みたいな感じで、「俺は型を宣言したいんじゃ!!!」という欲求に応えてくれた。
class Foo { public function something(int $x, int $y): int { return $x * $y; } }最近はこんな感じにスマートにかけてよかったね^^という気持ち
JSにもTypeScript以前にJSDocあったし、当然RubyにもRubyDocがあると思ってた。
そしたら、RubyDocはなんか違うやつだった。ドキュメント作るやつ。
ちゃうねん、そうじゃないねん。一応RDLってやつがあるみたいだけど、なんか違うというか・・・too muchというか・・・。
https://qiita.com/baban/items/0f782691b6e6e7213453
「Rubyの思想・文化的にそういう型宣言とは仲良くない」的なことだってのはわかってるんですが、「引数が何くるのかわからない状況ってのが怖くて、「え・・・string渡してくるバカがいたら死ぬの・・・?」みたいな気持ちになる。「string渡してくるやつがアホい」っていう思想なんだろうけど。
まとめ
なんか他にもいろいろあったと思うんですけど、思い出した範囲で感じた違和感をかきました。
別にRubyをディスってるわけじゃなくて「全然Rubyの世界観に馴染めてない俺」っていう感じです。
ワンライナー書いたりとかRspecのDSL感とか、書いててシックリくることも多いのですが、なんかやっぱ型システム周りの思想の違いは大きいなぁと思ってます。
ダックタイピングって誰得なのかがまだわかってないので、しっくりくる日を待ち望んでます。かしこ
- 投稿日:2019-12-15T11:47:18+09:00
(Laravel6.x)laravel/ui で 5.8 以前の make:auth のscaffolding する
Laravel6.x から make:auth コマンドが消え、react と vue のscaffolding に変わった??
php artisan make:auth Command "make:auth" is not defined.以前から laravel/ui に認証のscaffolding が移行されたから、という情報は認識していたのですが、最近 「laravel/ui の make:auth(ui:auth) では npm が必要で react が defaultになったらしい。」 というのを聞いて公式を調べたところ
vue が前提となっているように見えるので、今のプロジェクトではSPA用のJSは使っていないため bladeのscaffoldings がこのまま廃止されてしまったら、使いずらくなるなと思ったのですが、調べると、laravel/uiでも 5.8 以前の make:auth のscaffolding することができました。
0. 準備
laravel new make-auth-demo1. laravel/ui を取得
composer require laravel/ui --dev2. ui:auth --views を実行
php artisan ui:auth --viewsui/src/AuthCommand.php にがっつり記載がありました。
https://github.com/laravel/ui/blob/master/src/AuthCommand.php
ui/src/AuthCommand.php<?php namespace Laravel\Ui; use InvalidArgumentException; use Illuminate\Console\Command; class AuthCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'ui:auth { type=bootstrap : The preset type (bootstrap) } {--views : Only scaffold the authentication views} {--force : Overwrite existing views by default}'; /** * The console command description. * * @var string */ protected $description = 'Scaffold basic login and registration views and routes'; /** * The views that need to be exported. * * @var array */ protected $views = [ 'auth/login.stub' => 'auth/login.blade.php', 'auth/passwords/confirm.stub' => 'auth/passwords/confirm.blade.php', 'auth/passwords/email.stub' => 'auth/passwords/email.blade.php', 'auth/passwords/reset.stub' => 'auth/passwords/reset.blade.php', 'auth/register.stub' => 'auth/register.blade.php', 'auth/verify.stub' => 'auth/verify.blade.php', 'home.stub' => 'home.blade.php', 'layouts/app.stub' => 'layouts/app.blade.php', ]; /** * Execute the console command. * * @return void */ public function handle() { if (static::hasMacro($this->argument('type'))) { return call_user_func(static::$macros[$this->argument('type')], $this); } if (! in_array($this->argument('type'), ['bootstrap'])) { throw new InvalidArgumentException('Invalid preset.'); } $this->ensureDirectoriesExist(); $this->exportViews(); if (! $this->option('views')) { $this->exportBackend(); } $this->info('Authentication scaffolding generated successfully.'); } /** * Create the directories for the files. * * @return void */ protected function ensureDirectoriesExist() { if (! is_dir($directory = $this->getViewPath('layouts'))) { mkdir($directory, 0755, true); } if (! is_dir($directory = $this->getViewPath('auth/passwords'))) { mkdir($directory, 0755, true); } } /** * Export the authentication views. * * @return void */ protected function exportViews() { foreach ($this->views as $key => $value) { if (file_exists($view = $this->getViewPath($value)) && ! $this->option('force')) { if (! $this->confirm("The [{$value}] view already exists. Do you want to replace it?")) { continue; } } copy( __DIR__.'/Auth/'.$this->argument('type').'-stubs/'.$key, $view ); } } /** * Export the authentication backend. * * @return void */ protected function exportBackend() { file_put_contents( app_path('Http/Controllers/HomeController.php'), $this->compileControllerStub() ); file_put_contents( base_path('routes/web.php'), file_get_contents(__DIR__.'/Auth/stubs/routes.stub'), FILE_APPEND ); } /** * Compiles the "HomeController" stub. * * @return string */ protected function compileControllerStub() { return str_replace( '{{namespace}}', $this->laravel->getNamespace(), file_get_contents(__DIR__.'/Auth/stubs/controllers/HomeController.stub') ); } /** * Get full view path relative to the application's configured view path. * * @param string $path * @return string */ protected function getViewPath($path) { return implode(DIRECTORY_SEPARATOR, [ config('view.paths')[0] ?? resource_path('views'), $path, ]); } }以上です。
- 投稿日:2019-12-15T11:47:18+09:00
(Laravel6.x)laravel/ui で 5.8 以前の make:auth のscaffolding を する
Laravel6.x から make:auth コマンドが消え、react と vue のscaffolding に変わった??
php artisan make:auth Command "make:auth" is not defined.以前から laravel/ui に認証のscaffolding が移行されたから、という情報は認識していたのですが、最近 「laravel/ui の make:auth(ui:auth) では npm が必要で react が defaultになったらしい。」 というのを聞いて公式を調べたところ
vue が前提となっているように見えるので、今のプロジェクトではSPA用のJSは使っていないため bladeのscaffoldings がこのまま廃止されてしまったら、使いずらくなるなと思ったのですが、調べると、laravel/uiでも 5.8 以前の make:auth のscaffolding をすることができました。
0. 準備
laravel new make-auth-demo1. laravel/ui を取得
composer require laravel/ui --dev2. ui:auth --views を実行
php artisan ui:auth --viewsui/src/AuthCommand.php にがっつり記載がありました。
https://github.com/laravel/ui/blob/master/src/AuthCommand.php
ui/src/AuthCommand.php<?php namespace Laravel\Ui; use InvalidArgumentException; use Illuminate\Console\Command; class AuthCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'ui:auth { type=bootstrap : The preset type (bootstrap) } {--views : Only scaffold the authentication views} {--force : Overwrite existing views by default}'; /** * The console command description. * * @var string */ protected $description = 'Scaffold basic login and registration views and routes'; /** * The views that need to be exported. * * @var array */ protected $views = [ 'auth/login.stub' => 'auth/login.blade.php', 'auth/passwords/confirm.stub' => 'auth/passwords/confirm.blade.php', 'auth/passwords/email.stub' => 'auth/passwords/email.blade.php', 'auth/passwords/reset.stub' => 'auth/passwords/reset.blade.php', 'auth/register.stub' => 'auth/register.blade.php', 'auth/verify.stub' => 'auth/verify.blade.php', 'home.stub' => 'home.blade.php', 'layouts/app.stub' => 'layouts/app.blade.php', ]; /** * Execute the console command. * * @return void */ public function handle() { if (static::hasMacro($this->argument('type'))) { return call_user_func(static::$macros[$this->argument('type')], $this); } if (! in_array($this->argument('type'), ['bootstrap'])) { throw new InvalidArgumentException('Invalid preset.'); } $this->ensureDirectoriesExist(); $this->exportViews(); if (! $this->option('views')) { $this->exportBackend(); } $this->info('Authentication scaffolding generated successfully.'); } /** * Create the directories for the files. * * @return void */ protected function ensureDirectoriesExist() { if (! is_dir($directory = $this->getViewPath('layouts'))) { mkdir($directory, 0755, true); } if (! is_dir($directory = $this->getViewPath('auth/passwords'))) { mkdir($directory, 0755, true); } } /** * Export the authentication views. * * @return void */ protected function exportViews() { foreach ($this->views as $key => $value) { if (file_exists($view = $this->getViewPath($value)) && ! $this->option('force')) { if (! $this->confirm("The [{$value}] view already exists. Do you want to replace it?")) { continue; } } copy( __DIR__.'/Auth/'.$this->argument('type').'-stubs/'.$key, $view ); } } /** * Export the authentication backend. * * @return void */ protected function exportBackend() { file_put_contents( app_path('Http/Controllers/HomeController.php'), $this->compileControllerStub() ); file_put_contents( base_path('routes/web.php'), file_get_contents(__DIR__.'/Auth/stubs/routes.stub'), FILE_APPEND ); } /** * Compiles the "HomeController" stub. * * @return string */ protected function compileControllerStub() { return str_replace( '{{namespace}}', $this->laravel->getNamespace(), file_get_contents(__DIR__.'/Auth/stubs/controllers/HomeController.stub') ); } /** * Get full view path relative to the application's configured view path. * * @param string $path * @return string */ protected function getViewPath($path) { return implode(DIRECTORY_SEPARATOR, [ config('view.paths')[0] ?? resource_path('views'), $path, ]); } }以上です。
- 投稿日:2019-12-15T11:47:18+09:00
(Laravel6.x)laravel/ui で 5.8 以前の make:auth のscaffolding をする
Laravel6.x から make:auth コマンドが消え、react と vue のscaffolding に変わった??
php artisan make:auth Command "make:auth" is not defined.以前から laravel/ui に認証のscaffolding が移行されたから、という情報は認識していたのですが、最近 「laravel/ui の make:auth(ui:auth) では npm が必要で vue、react が defaultになったらしい。」 というのを聞いて公式を調べたところ
vue が前提となっているように見えるので、今のプロジェクトではSPA用のJSは使っていないため bladeのscaffoldings がこのまま廃止されてしまったら、使いずらくなるなと思ったのですが、調べると、laravel/uiでも 5.8 以前の make:auth のscaffolding をすることができました。
0. 準備
laravel new make-auth-demo1. laravel/ui を取得
composer require laravel/ui --dev2. ui:auth --views を実行
php artisan ui:auth --viewsui/src/AuthCommand.php にがっつり記載がありました。
https://github.com/laravel/ui/blob/master/src/AuthCommand.php
ui/src/AuthCommand.php<?php namespace Laravel\Ui; use InvalidArgumentException; use Illuminate\Console\Command; class AuthCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'ui:auth { type=bootstrap : The preset type (bootstrap) } {--views : Only scaffold the authentication views} {--force : Overwrite existing views by default}'; /** * The console command description. * * @var string */ protected $description = 'Scaffold basic login and registration views and routes'; /** * The views that need to be exported. * * @var array */ protected $views = [ 'auth/login.stub' => 'auth/login.blade.php', 'auth/passwords/confirm.stub' => 'auth/passwords/confirm.blade.php', 'auth/passwords/email.stub' => 'auth/passwords/email.blade.php', 'auth/passwords/reset.stub' => 'auth/passwords/reset.blade.php', 'auth/register.stub' => 'auth/register.blade.php', 'auth/verify.stub' => 'auth/verify.blade.php', 'home.stub' => 'home.blade.php', 'layouts/app.stub' => 'layouts/app.blade.php', ]; /** * Execute the console command. * * @return void */ public function handle() { if (static::hasMacro($this->argument('type'))) { return call_user_func(static::$macros[$this->argument('type')], $this); } if (! in_array($this->argument('type'), ['bootstrap'])) { throw new InvalidArgumentException('Invalid preset.'); } $this->ensureDirectoriesExist(); $this->exportViews(); if (! $this->option('views')) { $this->exportBackend(); } $this->info('Authentication scaffolding generated successfully.'); } /** * Create the directories for the files. * * @return void */ protected function ensureDirectoriesExist() { if (! is_dir($directory = $this->getViewPath('layouts'))) { mkdir($directory, 0755, true); } if (! is_dir($directory = $this->getViewPath('auth/passwords'))) { mkdir($directory, 0755, true); } } /** * Export the authentication views. * * @return void */ protected function exportViews() { foreach ($this->views as $key => $value) { if (file_exists($view = $this->getViewPath($value)) && ! $this->option('force')) { if (! $this->confirm("The [{$value}] view already exists. Do you want to replace it?")) { continue; } } copy( __DIR__.'/Auth/'.$this->argument('type').'-stubs/'.$key, $view ); } } /** * Export the authentication backend. * * @return void */ protected function exportBackend() { file_put_contents( app_path('Http/Controllers/HomeController.php'), $this->compileControllerStub() ); file_put_contents( base_path('routes/web.php'), file_get_contents(__DIR__.'/Auth/stubs/routes.stub'), FILE_APPEND ); } /** * Compiles the "HomeController" stub. * * @return string */ protected function compileControllerStub() { return str_replace( '{{namespace}}', $this->laravel->getNamespace(), file_get_contents(__DIR__.'/Auth/stubs/controllers/HomeController.stub') ); } /** * Get full view path relative to the application's configured view path. * * @param string $path * @return string */ protected function getViewPath($path) { return implode(DIRECTORY_SEPARATOR, [ config('view.paths')[0] ?? resource_path('views'), $path, ]); } }参考
下記の記事に詳しく書いて頂いておりましたので参考にさせて頂きました。
https://medium.com/@panjeh/laravel-changes-in-php-artisan-ui-auth-php-artisan-make-auth-82fdb8893726以上です。
- 投稿日:2019-12-15T11:06:02+09:00
【Laravel】Todoリストアプリの作成
laravelでtodoアプリを作成
laravelでのCRUD処理理解のために、todoアプリを作成しました。その作成方法のメモです。
.envファイルの修正
.envDB_CONNECTION=sqliteデータベースファイルの作成
$ cd database $ touch database.sqliteテーブル設計
- モデル名は「Todo」とします
- テーブル名は「todos」になります。
- コントローラー名は「TodosController」とします。
Todosテーブルのカラム構成
- id
- body:todoの内容
- created_at
- updated_at
※idとcreated_atとupdate_atは自動で作成されます。
モデルとマイグレーションファイルの作成
$ cd .. (プロジェクトフォルダ直下に戻る) $ php artisan make:model Todo --migration Model created successfully. Created Migration: 2019_12_14_015752_create_todos_tableマイグレーションファイルの修正
database/migrations/2019_12_14_015752_create_todos_tablepublic function up() { Schema::create('todos', function (Blueprint $table) { $table->bigIncrements('id'); $table->text('body'); //追加 $table->timestamps(); }); }マイグレーションの実行
$ php artisan migrate Migration table created successfully. Migrating: 2019_12_14_015752_create_todos_table Migrated: 2019_12_14_015752_create_todos_table (0.01 seconds)テーブルが作成されているか確認する
todosと表示されていれば、テーブルが作成されている。
$ sqlite3 database/database.sqlite SQLite version 3.7.17 2013-05-20 00:56:22 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> sqlite> .tables //テーブル一覧を取得 migrations todos sqlite> PRAGMA table_info('todos'); //テーブル構造(カラム)を確認 0|id|integer|1||1 1|body|text|1||0 2|created_at|datetime|0||0 3|updated_at|datetime|0||0 sqlite> .q //sqliteを閉じる $コントローラーの作成
$ php artisan make:controller TodosControllerルーティングの設定
routes/web.php<?php /* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */ //以下をコメントアウトする //Route::get('/', function () { // return view('welcome'); //}); Route::get('/','TodosController@index'); Route::resource('todos','TodosController');resourcesでURIやコントローラーのメソッドは作成されるが、初期画面をindexにしたいので、Route::get・・・も追加する。
以下を参考にして表を作成
https://laraweb.net/knowledge/725/
HTTPメソッド URI コントローラのメソッド 用途 GET /todos index() 一覧表示 GET /todoss/create create() 追加ページ POST /todos store() 追加 GET /todos/{id} show() 該当データ表示 GET /todos/{id}/edit edit() 更新ページ PUT /todos/{id} update() 更新 DELETE /todos/{id} destroy() 削除 Todosコントローラーの修正
app/Http/Controllers/TodosController.php<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class TodosController extends Controller { //追加 public function index() { return view('todos.index'); } //追加 }todosテーブルに初期データを流し込む為のseederファイルを生成
$ php artisan make:seeder TodoTableSeeder Seeder created successfully.作成したTodoTableSeederに流し込むデータ記載する
database/seeds/TodoTableSeeder.php<?php use Illuminate\Database\Seeder; class TodoTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { // $bodys = ['JCBギフト券を換金する','ゼルダの伝説をする','conohaのVPSで立ち上げをする']; foreach ($bodys as $body) { DB::table('todos')->insert([ 'body' => $body, 'created_at' => new Datetime(), 'updated_at' => new Datetime() ]); } } }seederを実行(テーブルへの初期データ書き込み)するためにDatabaseSeederに記載する
database/seeds/DatabaseSeeder.php<?php use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { /** * Seed the application's database. * * @return void */ public function run() { // $this->call(UsersTableSeeder::class); $this->call(TodoTableSeeder::class); } }seederを実行してtodosテーブルに書き込みする。
$ php artisan db:seed Seeding: TodoTableSeeder Database seeding completed successfully.todosテーブルに書き込まれているか確認する
$ sqlite3 database/database.sqlite SQLite version 3.7.17 2013-05-20 00:56:22 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> select * from todos; 1|JCBギフト券を換金する|| 2|ゼルダの伝説をする|| 3|conohaのVPSで立ち上げをする||Viewの部分を作成していきます。
index.balde.phpを作成する
$ cd resources/views $ mkdir todos (todos関連のディレクトリを作成) $ vi index.blade.php (viewファイルの作成)bootstrap4を使います。
以下のリンクからコピーしましょう。
https://cccabinet.jpn.org/bootstrap4/getting-started/introduction
resources/views/index.blade.php<!doctype html> <html lang="ja"> <head> <title>Bootstrap基本テンプレート</title> <!-- 必要なメタタグ --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"> </head> <body> <h1>こんにちは!</h1> <!-- オプションのJavaScript --> <!-- 最初にjQuery、次にPopper.js、次にBootstrap JS --> <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script> </body> </html>Todoリストの見た目の部分を作成します。
変更内容は以下
- もろもろ
- todo作成フォーム
- todo追加ボタン
- todo編集ボタン
- todo削除ボタン
rescources/views/todos/index.blade.php<!doctype html> <html lang="ja"> <head> <title>Jum Todoリスト</title> <!-- 必要なメタタグ --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"> </head> <body> <div class="container" style="margin-top:50px;"> <h1>Todoリスト追加</h1> <form action='{{ url('/todos')}}' method="post"> {{csrf_field()}} <div class="form-group"> <label >やることを追加してください</label> <input type="text" name="body"class="form-control" placeholder="todo list" style="max-width:1000px;"> </div> <button type="submit" class="btn btn-primary">追加する</button> </form> <h1 style="margin-top:50px;">Todoリスト</h1> <table class="table table-striped" style="max-width:1000px; margin-top:20px;"> <!-- <thead> <tr> <th></th><th></th><th></th> </tr> </thead> --> <tbody> @foreach ($todos as $todo) <tr> <td>{{$todo->body}}</td> <td><form action="{{ action('TodosController@edit', $todo) }}" method="post"> {{ csrf_field() }} {{ method_field('get') }} <button type="submit" class="btn btn-primary">編集</button> </form> </td> <!-- 削除ボタン --> <td><form action="{{url('/todos', $todo->id)}}" method="post"> {{ csrf_field() }} {{ method_field('delete') }} <button type="submit" class="btn btn-danger">削除</button> </form> </td> <!-- 削除した際にポップ画面で確認をする --> <!-- <td><a class="del" data-id="{{ $todo->id }}" href="#">削除</a> <form method="post" action='{{ url('/todos', $todo->id) }}' id="form_{{ $todo->id}}"> {{ csrf_field() }} {{ method_field('delete') }} </form> </td> --> </tr> @endforeach </table> </div> <!-- オプションのJavaScript --> <!-- 最初にjQuery、次にPopper.js、次にBootstrap JS --> <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script> <script> (function() { 'use strict'; var cmds = document.getElementsByClassName('del'); var i; for (i = 0; i < cmds.length; i++) { cmds[i].addEventListener('click', function(e) { e.preventDefault(); if (confirm('are you sure?')) { document.getElementById('form_' + this.dataset.id).submit(); } }); } })(); </script> </body> </html>コントローラーの修正
todo追加処理
app/Http/controllers/TodosController.phppublic function store(Request $request) { $todo = new Todo(); $todo->body = $request->body; $todo->save(); return redirect('/'); }削除処理
app/Http/controllers/TodosController.phppublic function destroy(todo $todo) { $todo->delete(); return redirect('/'); }編集処理
編集する為のviewを作成
$ cd resources/views/todos $ touch edit.blade.phpedit.blade.phpを編集
resources/views/todos/edit.blade.php<!doctype html> <html lang="ja"> <head> <title>Jum Todoリスト</title> <!-- 必要なメタタグ --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"> </head> <body> <div class="container" style="margin-top:50px;"> <h1>Todoリスト更新</h1> <form action='{{ url('/todos',$todo->id) }}' method="post"> {{csrf_field()}} {{ method_field('patch')}} <div class="form-group"> <label >やることを更新してください</label> <input type="text" name="body"class="form-control" value="{{ $todo->body }}" style="max-width:1000px;"> </div> <button type="submit" class="btn btn-primary">更新する</button> </form> </div> <!-- オプションのJavaScript --> <!-- 最初にjQuery、次にPopper.js、次にBootstrap JS --> <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script> <script> (function() { 'use strict'; var cmds = document.getElementsByClassName('del'); var i; for (i = 0; i < cmds.length; i++) { cmds[i].addEventListener('click', function(e) { e.preventDefault(); if (confirm('are you sure?')) { document.getElementById('form_' + this.dataset.id).submit(); } }); } })(); </script> </body> </html>編集画面への遷移処理(コントローラー)
app/Http/controllers/TodosController.phppublic function edit(todo $todo) { return view('todos.edit')->with('todo',$todo); }更新処理(コントローラー)
app/Http/controllers/TodosController.phppublic function update(Request $request,todo $todo) { $todo->body = $request->body; $todo->save(); return redirect('/'); }とりあえず完了
ブラウザで確認
$ php artisan serve --host 192.168.33.10 --port 8000 Laravel development server started: <http://192.168.33.10:8000>初期画面(index)
編集画面(edit)
その他の機能
バリデーション
フラッシュメッセージ
終わりに
以上です。
これからlaravelを始める方は参考にしてみてください。
- 投稿日:2019-12-15T10:28:40+09:00
Formファサードでバリデーションエラー時にCSSクラスをあてる
バリデーションエラー時に、Formファザードで作ったタグにエラー用のCSSクラスをあてる方法を調べました。
ルーティングと簡単なコントローラーを用意します。
routes/web.phpRoute::get('/form', 'FormController@index'); Route::post('/form', 'FormController@post');app/Http/Controllers/FormController.php<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class FormController extends Controller { public function index() { $list = [ '' => '選択してください', '1' => 'ほげ', '2' => 'ふが', ]; return view('form', compact('list')); } public function post(Request $request) { $request->flash(); $request->validate([ 'text' => 'required', 'id' => 'required', ],[ 'text.required' => 'テキストを入力してください。', 'id.required' => 'リストを選択してください。', ]); return redirect('form')->with('flash_message', '送信完了しました。'); } }テンプレートを用意します。
デフォルトのレイアウトとCSSを流用しています。resources/views/Form.blade.php@extends('layouts.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> @if (session('flash_message')) <div class="alert alert-success" role="alert"> {{ session('flash_message') }} </div> @endif <div class="card"> <div class="card-header">フォーム</div> <div class="card-body"> {{Form::open(['url' => '/form'])}} {{Form::token()}} <div class="form-group"> {{Form::text('text', old('text'), $errors->has('text') ? ['class' => 'form-control is-invalid', 'placeholder' => '入力してください'] : ['class' => 'form-control', 'placeholder' => '入力してください'])}} <span class="invalid-feedback" role="alert">@error('text'){{ $message }}@enderror</span> </div> <div class="form-group"> {{Form::select('id', $list, old('id'), $errors->has('id') ? ['class' => 'form-control is-invalid'] : ['class' => 'form-control'] )}} <span class="invalid-feedback" role="alert">@error('id'){{ $message }}@enderror</span> </div> {{Form::submit('送信', ['class'=>'btn btn-primary'])}} {{Form::close()}} </div> </div> </div> </div> </div> @endsection三項演算子で、
$errors->has('id') ? ['class' => 'form-control is-invalid'] : ['class' => 'form-control']
のようにエラーの有無でクラスの配列引数を変えています。バリデーションエラーがあるとこんな感じでエラーの装飾があたります。
- 投稿日:2019-12-15T06:18:02+09:00
LaravelのViewComposerで継承を使わずに共通化する
この記事はLaravel Advent Calendar 2019の15日目の記事です。
ViewComposerとは?
ビューコンポーザはビューがレンダーされる時に呼び出される、コールバックかクラスメソッドのことです。ビューがレンダーされるたびに結合したい情報があるなら、ビューコンポーザがロジックを一箇所にまとめるのに役立ちます。
ビュー 6.x Laravel基本的な実装方法も上記Laravelのドキュメントで確認することができます。
ViewComposerの実装を共通化する
本記事ではサンタクロース(santa-claus)画面とトナカイ(reindeer)画面にそれぞれViewComposerを定義し、
それぞれ共通してisChristmas
をViewから参照させます。
これを継承を使った方法を紹介した上で、継承を使わずに共通化する方法を紹介します。継承を使った実装
isChristmas
を設定するChristmasViewComposer
を実装し、SantaClausViewComposer
とReindeerViewComposer
がそのクラスを継承します。namespace App\Http\View\Composers; use Illuminate\View\View; class ChristmasViewComposer { public function compose(View $view) { $view->with('isChristmas', $this->isChristmas()); } private function isChristmas() { return false; // FIXME 適当な実装 } } class SantaClausViewComposer extends ChristmasViewComposer { public function compose(View $view) { parent::compose($view); // FIXME サンタクロース(santa-claus)画面に必要な実装 } } class ReindeerViewComposer extends ChristmasViewComposer { public function compose(View $view) { parent::compose($view); // FIXME トナカイ(reindeer)画面に必要な実装 } }コンポーザーの定義はこのようになります。
View::composer('santa-claus', 'App\Http\View\Composers\SantaClausViewComposer'); View::composer('reindeer', 'App\Http\View\Composers\ReindeerViewComposer');継承を使わない実装
isChristmas
を設定するChristmasViewComposer
を実装しますが、SantaClausViewComposer
とReindeerViewComposer
はそれを継承しません。namespace App\Http\View\Composers; use Illuminate\View\View; class ChristmasViewComposer { public function compose(View $view) { $view->with('isChristmas', $this->isChristmas()); } private function isChristmas() { return false; // FIXME 適当な実装 } } class SantaClausViewComposer { public function compose(View $view) { // FIXME サンタクロース(santa-claus)画面に必要な実装 } } class ReindeerViewComposer { public function compose(View $view) { // FIXME トナカイ(reindeer)画面に必要な実装 } }継承していないためコンポーザーをこのように定義します。
View::composer( ['santa-claus', 'reindeer'], 'App\Http\View\Composers\ChristmasViewComposer' ); View::composer('santa-claus', 'App\Http\View\Composers\SantaClausViewComposer'); View::composer('reindeer', 'App\Http\View\Composers\ReindeerViewComposer');まとめ
ドキュメントには書かれていませんが、一つのViewに対して複数の複数のViewComposerを登録することも可能なため、継承を使わなくてもViewComposerの実装を共通化できます。
クラス継承にはデメリットが多いため、クラス継承を使わない共通化の方法を紹介しました。