- 投稿日:2020-10-20T22:50:39+09:00
[Codewars] Build Tower
概要
Codewarsの問題
Build Tower
の回答の復習とベストプラクティスをまとめる個人メモです。問題
数値が渡ってくるので、以下のように*の数値分の階層のピラミッドを作成する問題です。
Build Tower by the following given argument:
number of floors (integer and always greater than 0).
Tower block is represented as *[ ' * ', ' *** ', '*****' ] [ ' * ', ' *** ', ' ***** ', ' ******* ', ' ********* ', '***********' ]回答
規則性として、最大のアスタリスクの数は
$n * 2 -1
、アスタリスクの数が$i * 2 -1
、空白の数が*の最大値 - *の数 / 2
と定義できます。
数を決めた後、str_repeat
を使用しピラミッドを表現させます。function tower_builder(int $n): array { $max = $n * 2 - 1; $array = []; for($i=1;$i <= $n;$i++){ $asterisk = $i * 2 - 1; $empty = ($max - $asterisk) / 2; $result = str_repeat(" ", $empty) . str_repeat("*", $asterisk) . str_repeat(" ", $empty); array_push($array, $result); } return $array; }ベストプラクティス
str_pad
を使用し文字列を埋めています。
STR_PAD_BOTH
を引数に取ることで、第一引数の値が真ん中にくるようになっています。
https://www.php.net/manual/ja/function.str-pad.phpfunction tower_builder(int $n): array { $pad = $n * 2 - 1; $x = 1; $arr = []; while ($n --> 0) { $arr[] = str_pad(str_repeat('*', $x), $pad, ' ', STR_PAD_BOTH); $x += 2; } return $arr; }
- 投稿日:2020-10-20T22:48:05+09:00
phpで簡単パイプライン関数
関数型プログラミングを学習する中でパイプライン演算子に出会い、惚れてしまいました。phpで似たようなことができないかと思い、今回の実装を行いました。
まだまだ若輩者ですのでアドバイス等いただけると嬉しいです。
前提
- 単一引数の関数を対象とする
- 中置記法が使えないので |> の形は諦める
- array_mapみたく不格好でもいいからとりあえずpiplineぽいことがしたい
ソース(github)
<?php function array_pipeline($data, array $functions) { $recursive = function ($data, $count = 0) use ($functions, &$recursive) { if ($count === count($functions)) return $data; return $recursive($functions[$count]($data), $count+1); }; return $recursive($data); }実装のポイント
- phpは「'関数名'()」で関数を呼べるので、関数名をarrayで受け取ることにした
- 関数内関数を無名関数にしないと再帰的に関数を定義してしまい、エラーになる
- 無名関数を再帰的に実行するにはuseで変数を渡すときに参照渡しにする必要がある
使用例
<?php require 'pipeline.php'; function double($data) { return $data * 2; } function addTwo($data) { return $data + 2; } echo array_pipeline(5, ['double', 'addTwo']); // 12 echo "\n"; echo array_pipeline(5, ['addTwo', 'double']); // 14今後の展望
array_pipeline($data, [['add', 3], ['minus', 7]]);
のようにして単一引数の関数という制約を外したい
- 投稿日:2020-10-20T21:41:24+09:00
Laravel/PHP タグ付き投稿の検索機能(orWhereHasと無名関数、クロージャ)
やりたいこと
ジャズライブの口コミをタグ付きで投稿できるサイトを、チームで開発しています。
口コミ投稿をキーワード検索するコードをネットを参考にしながら書いたのですが、一部読み解けなかった部分を整理したいと思います。仕様
- キーワードが入力されると、そのキーワードを含む投稿を検索しビューに表示する。
- 検索対象は、投稿の(1)タイトル、(2)本文、(3)投稿につけられたタグ。
- 投稿のテーブル、タグのテーブルはEloquentでModelを利用している。
- 投稿のテーブルとタグのテーブルが中間テーブルを挟んで多対多のリレーションでつながっている。
実装してみた
コントローラの実装例
ルート、ビューなどは省略させていただきます。もし見せて!という方いたらコメントください(いないと思いますが汗)。
SearchController.php<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Review; use App\Tag; class SearchController extends Controller { public function search(Request $request) { $keyword = $request->input('keyword'); if (!empty($keyword)) { $reviews = Review::where('title', 'like', '%' . $keyword . '%') ->orWhere('text', 'like', '%' . $keyword . '%') ->orWhereHas('tags', function ($query) use ($keyword){ $query->where('tag_name', 'like', '%' . $keyword . '%'); }) ->paginate(10); } else { return redirect('/'); } return view('reviews.search', [ 'reviews' => $reviews ]); } }orWhereHasからの2行がよくわからない!!ココを解明したい!!
orWhereHasとは?
->orWhereHas('tags', function ($query) use ($keyword){ $query->where('tag_name', 'like', '%' . $keyword . '%');orWhereHasはEloquentのメソッド、関数。わかってたふりしてましたが、案外うまく説明できなかったので整理。公式ドキュメント→Eloquent:存在するリレーションのクエリ
orは、2つ以上の条件をつけて「AもしくはB」としたい時に、2つめ以降の条件のメソッドにつける言葉。
Whereは、SQLのわかる方なら想像しやすいかもですが、問い合わせに条件をつけるもの。例えばidがホニャララなもの、とかcreated_atがいついつ以前のものといった条件が想定できるかと思います。
Hasはあるモデルからリレーション先のテーブルに対してレコードを探してくれるメソッド。リレーション名を第一パラメータに渡します。例えば、以下のようにするとタグの付いたレビューを取得するという命令になります。$reviews = Review::has('tags')->get();まとめるとorWhereHasは、リレーション先にレコードを(Has)、条件付きで(Where)、並列の条件(or)として探しにいく時に便利なEloquentのメソッドです。
orWhereHasの第二パラメータの関数は何?
->orWhereHas('tags', function ($query) use ($keyword){ $query->where('tag_name', 'like', '%' . $keyword . '%');このfunctionから始まる2行は、無名関数、またの名をクロージャというやつみたいです。公式→PHP 無名関数。名前ないですものね。無名関数はどういう時に使うかというと、プログラムの中でその場で一度しか使わないものに適切みたいです。ちなみに、Closureというクラスで実装されているそうです。紐解いていきます。
順番前後しますが、
use ($keyword)
は、その関数の親のスコープから変数$keyordを引き継ぐ時にuseで渡すようです。
($query)
は変数ではなく引数。通常の引数を受け付けています。この無名関数の中では、「tag_nameに$keywordを含んだレコードを」という条件設定をしているwhereメソッドにアクセスしています。総合して一言でこの第2パラメータを説明すると、親スコープの$keywordを引き継いで作成する、whereという条件設定をする関数という感じでしょうか。
最後に
もしご指摘などあれば、バシバシお願い申し上げます!
- 投稿日:2020-10-20T15:39:28+09:00
【Laravel】public/css内のCSSを読み込まないエラー 〜 net::ERR_CONNECTION_CLOSED〜
【背景】
AWSからVagrantにローカル環境を変えて、開発を行う。
Viewを作成時に、コンパイルしたcssを読み込もうしても、Viewに反映されず行き詰まる。【状況の確認】
Bladeファイルのheadタグ内にCSSを読み込む記述がされているか確認。
<link href="{{ secure_asset('css/app.css') }}" rel="stylesheet"> <link href="{{ secure_asset('css/style.css') }}" rel="stylesheet">次にchromeの検証ツールでエラー内容を確認。エラーは検証ツール画面の右上の設定マークの横にある赤いバツ❌をクリックします。
net::ERR_CONNECTION_CLOSED と表示されています。
どうやら接続ができない様子。Javascriptの方もできていない。【解決法】
Blade内のsecure_の部分を消す。
<link href="{{ asset('css/app.css') }}" rel="stylesheet"> <link href="{{ asset('css/style.css') }}" rel="stylesheet">同ファイル内のJavascriptの部分も直します。
<script src="{{ asset('js/app.js') }}" defer></script>これでブラウザで確認すればCSSが反映されていると思います。
【原因】
Web環境のhttpsを使った環境だとsecure_を付ける必要がある。
今回、開発環境をAWSからローカル環境のVagrantに変えたので、記述を変える必要があったようです。
- 投稿日:2020-10-20T14:07:11+09:00
php slimのRequestオブジェクトからリクエストURLの絶対URLを取得する
困ったこと
PHPのSlim フレームワークで開発をしていてリクエストの絶対URLが取得したかった。
TL;DR
ストリングでキャストするとでてくる。
(string)$this->request->getUri();経緯
slimで開発をしていて、ちょっとリクエストされたURIをセッションに保存して、別の処理をした後に、セッションから読み出して、リダイレクトで戻りたかった。
PSR-7のRequestオブジェクトが、こんな感じで、これを自分で結合していくのは面倒。
何かよい方法あるはずって探した。
> $this->request->getUri() < Slim\Psr7\Uri::__set_state(array( 'scheme' => 'https', 'user' => '', 'password' => '', 'host' => 'example.com', 'port' => 443, 'path' => '/oauth2/authorize', 'query' => 'state=xxxxxx', 'fragment' => '', ))キャストするだけだった(笑)
> (string)$this->request->getUri() < https://example.com/oauth2/authorize?state=xxxxx参考文献
https://discourse.slimframework.com/t/building-the-full-request-url/626
- 投稿日:2020-10-20T11:34:18+09:00
PHPからPDOを使ってSQLServerのストアドプロシージャを呼び出す際にOUTPUTが効かなくなる現象への対応
ドはまりしてしまったので、後学のためにメモを残します。
事象
次のようなスクリプトで、データ投入用のテーブルおよびプロシージャを作成します。
-- データ投入用のテーブル作成 create table test_table ( col1 varchar(50), col2 int, ); -- プロシージャ作成 create procedure sample @in varchar(50), @out int OUTPUT as set @out = 567; insert into test_table values (@in, @out); ;PDOから次のようなスクリプトでプロシージャを実行します。
$pdo = new PDO($dsn, $username, $password); $stmt = $pdo->prepare('{CALL sample (?, ?)}'); // 'EXEC sample ?, ?'でもOK $in = 'xyz'; $stmt->bindParam(1, $in, PDO::PARAM_STR); $stmt->bindParam(2, $out, PDO::PARAM_INT|PDO::PARAM_INPUT_OUTPUT); $stmt->execute(); echo $out;プロシージャ内で
set @out = 567
と記述したので$outには567が設定されており、結果として567
が出力される想定でしたが、結果が異なりました。プロシージャがうまく動いているかどうか確認するため、作成したテーブルに格納されたデータを取得したところ、想定通りのデータが入っており、特に問題なさそうでした。
select * from test_table; -- 「'xyz', 567」が取得できるここで、プロシージャ内のinsert文をコメントアウトしたところ、想定通りのOUTPUT変数の使い方が出来ました。
-- プロシージャ作成 alter procedure sample @in varchar(50), @out int OUTPUT as set @out = 567; -- insert into test_table values (@in, @out); ;$pdo = new PDO($dsn, $username, $password); $stmt = $pdo->prepare('{CALL sample (?, ?)}'); // 'EXEC sample ?, ?'でもOK $in = 'xyz'; $stmt->bindParam(1, $in, PDO::PARAM_STR); $stmt->bindParam(2, $out, PDO::PARAM_INT|PDO::PARAM_INPUT_OUTPUT); $stmt->execute(); echo $out; //「567」が出力される解決策
プロシージャ内で SET NOCOUNT ON の記述を入れることで、PHP側の変数に値が入るようになります。
-- プロシージャ作成 alter procedure sample @in varchar(50), @out int OUTPUT as set @out = 567 SET NOCOUNT ON insert into test_table values (@in, @out) ;$pdo = new PDO($dsn, $username, $password); $stmt = $pdo->prepare('{CALL sample (?, ?)}'); // 'EXEC sample ?, ?'でもOK $in = 'xyz'; $stmt->bindParam(1, $in, PDO::PARAM_STR); $stmt->bindParam(2, $out, PDO::PARAM_INT|PDO::PARAM_INPUT_OUTPUT); $stmt->execute(); echo $out; //「567」が出力されるSET NOCOUNT ONとは
https://docs.microsoft.com/ja-jp/sql/t-sql/statements/set-nocount-transact-sql?view=sql-server-ver15
Transact-SQL ステートメントまたはストアド プロシージャで処理された行数を示すメッセージが結果セットの一部として返されないようにします。
参考
この記事のおかげで助かりました。
https://trentrichardson.com/making-sense-of-stored-procedures-with-php-pdo-and-sqlsrv.htmlYou also may notice the empty result set at position 0. This is the insert query (not the first select query as it is simply assigning a value). To suppress these you should use "SET NOCOUNT ON" in your stored procedure. At that point insert, update and deletes will not return these empty sets.
- 投稿日:2020-10-20T11:25:46+09:00
PHP 多次元配列
初めてのPHPの投稿になります。
今回はPHPの多次元配列を備忘録として投稿させて頂きます。多次元配列とは
配列の中に配列を定義することを指します。
また、横と縦に並んでいることをイメージするとよりわかりやすいでしょう。
これを多次元配列」と呼び、よくプログラムコードでも使用される配列の形です。考え方
下記にて分かりやすい図がありましたので参照して下さい。
横のHairetu (0) (1) (2)から数えます。
次に縦のHairetu (0) (1) (2) (3)から数えます。
これでどこの要素を取り出したいかを指定できます。サンプルコードを掲載します。
sample.phpこちらの配列は分かりやすい様に記述したものです。 0 1 2 0[["1","2","3"], 1["A","B","C"], 2["a","b","c"]]; <?php $member = [["1","2","3"],["A","B","C"],["a","b","c"]]; echo ($member[2][2]);//c echo ($member[1][1]);//B echo ($member [0][0])//0 ?>多次元配列を最初見たときは何これと思いましたがよくよく考えたら分かりやすかったです。
- 投稿日:2020-10-20T10:25:11+09:00
php クラス
- 投稿日:2020-10-20T09:50:00+09:00
Laravelで超シンプルなToDoアプリを作成する
はじめに
Laravelで超シンプルなToDoアプリを作成する手順を紹介します。
ToDoアプリは、CRUD(Create, Read, Update, Delete)の操作を兼ね揃えた最もシンプルなアプリの一つと言えます。
そのため、学習したい言語やフレームワークを用いてToDoアプリを作成することで、その言語やフレームワークを用いたデータの作成・読み取り・更新・削除といった基本操作を一通り学習することができます。Laravelを用いてToDoアプリを作成するチュートリアルは公式のものも含めて既にいくつかあると思いますが、この記事では超シンプル・必要最低限の機能だけに絞ったToDoアプリを作成する手順を紹介します。
まずは最低限のToDoアプリを作成してみてLaravelのCRUDアプリ作成の勘所を押さえ、そこから自分が欲しいと思う機能を少しずつ加えながら学習を更に進めるのが良いと思います。作成するアプリの概要
画面構成
超シンプルにしてLaravelによるCRUD操作に内容をフォーカスしたいので、CSSはほぼ設定していません。
見た目はイケてませんが、そこは機能を実現してから各自の好みで装飾してください。タスク一覧ページ
タスクの一覧を表示するためのページです。
このページからタスク作成ページ・タスク編集ページ・タスク削除ページへ遷移することができます。このページのURLは、アプリのルート(
/
)にします。タスク作成ページ
新しいタスクを作成するためのページです。
タスクの情報をフォームに入力し、"追加"ボタンを押すことで新しいタスクを作成することができます。このページのURLは
/create-page
にします。
また、追加ボタンが押されて実際にタスクを追加するAPIのURLは/create
とします。タスク編集ページ
選択したタスクの内容を編集するためのページです。
選択したタスクの内容が予めフォームに入力されているので、編集したい箇所を書き換えて"修正"ボタンを押すことで内容を書き換えることができます。このページのURLは
/edit-page
にします。
また、修正ボタンが押されて実際にタスクを修正するAPIのURLは/edit
とします。タスク削除ページ
選択したタスクを削除するためのページです。
選択したタスクの内容が表示されるので、確認の上で"削除"ボタンを推すことでタスクを削除することができます。このページのURLは
/delete-page
にします。
また、削除ボタンが押されて実際にタスクを修正するAPIのURLは/delete
とします。URL設計
このアプリで使用するURLは以下のとおりです。
URL HTTPリクエストメソッド 用途 / GET アプリのトップページをレスポンスで返す /create-page GET タスクの作成ページをレスポンスで返す /create POST タスクの作成を実行する /edit-page GET タスクの編集ページをレスポンスで返す /edit POST タスクの編集を実行する /delete-page GET タスクの削除ページをレスポンスで返す /delete POST タスクの削除を実行する テーブル構成
このアプリは、作成したタスクをデータベース(SQLite)に保存します。
使用するテーブルは"todos"テーブルひとつだけです。本格的なアプリのデータベースとしてはSQLiteは力不足ですが、LaravelによるCRUDアプリの作成方法にフォーカスしたいので、まずは使うまでの準備が超簡単なSQLiteで慣れましょう。
作成したアプリをサーバで動かしたいといったステップまで進んだら、mysqlやpostgresなどのもう少し本格的なデータベースに切り替えることを検討すればよいと思います。todosテーブルの構成
todosテーブルは以下のようなカラムを持ちます。
カラムの論理名 カラムの物理名 id id タスクの名前 task_name タスクの説明 task_description 担当者の名前 assign_person_name 見積時間(h) estimate_hour 開発環境を整える
PHPとcomposerのインストール
こちらのページを参考にさせていただきました。
Macの場合でもほぼ変わりありませんので、composerのインストールまで済ませておきましょう。
Laravelチュートリアル1 – 環境構築編(Windows)上記のチュートリアルですが、2以降も大変参考になるので、Laravel初学者は一通り手を動かしてみることをお勧めします。
Laravelチュートリアル2 – ルーティングを理解しよう
Laravelチュートリアル3 -コントローラーで簡単な処理を書いてみよう
Laravelチュートリアル4 -データベースを使用して処理を書いてみよう
Laravelチュートリアル5 – フォームを扱った処理を書いてみようSQLite3のインストール
初期データを作成するためにSQLite3も使用します。
以下のページを参考にSQLite3もインストールしておきましょう。
Laravelでsqliteをデータベースとして使用する方法開発の流れ
超シンプルなToDoアプリを作る流れを順を追って解説します。
Laravelプロジェクトの作成
Laravelのプロジェクトを作成したいディレクトリをターミナルで開き、以下のコマンドを実行します。
composer create-project laravel/laravel=5.7 laravel_todolist
Laravelプロジェクトの作成が成功したら、以下のコマンドで"laravel_todolist"ディレクトリに入り、開発サーバを起動します。
cd laravel_todolist php artisan serve
http://127.0.0.1:8000
へアクセスして、Laravelと表示さてれいれば、開発サーバの起動は成功しています。データベースを用意
sqliteファイルの作成
作成した"laravel_todolist"ディレクトリで以下のコマンドを実行してsqliteファイルを作成します。
touch database/database.sqlite
LaravelプロジェクトのDB接続情報を書き換え
"laravel_todolist"ディレクトリにある
.env
ファイルをvi等で開いて内容を書き換えます。vi .env書き換え対象は以下の行です。
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=homestead DB_USERNAME=homestead DB_PASSWORD=secretSQLiteを使用するために以下のような内容に書き換えて保存します。
(#
の行はSQLiteでは使わないのでコメントアウトしています)DB_CONNECTION=sqlite # DB_HOST=127.0.0.1 # DB_PORT=3306 # DB_DATABASE=homestead # DB_USERNAME=homestead # DB_PASSWORD=secretマイグレーションの実行
"laravel_todolist"ディレクトリで以下のコマンドを実行し、マイグレーションを実行してテーブルを作成します。
php artisan migrateLaravelはデフォルトでusersテーブルとpassword_resetsテーブルのマイグレーションファイルを持っているので、
migrate
コマンドを実行することでusersテーブルとpassword_resetsテーブルがデータベースに作成されます。SQLite3による接続確認
"laravel_todolist"ディレクトリで以下のコマンドを実行してデータベースに接続します。
sqlite3 database/database.sqlite接続ができると、ターミナルが以下のような表示になります。
この画面でSQLを入力・実行することができます。SQLite version 3.28.0 2019-04-15 14:49:49 Enter ".help" for usage hints. sqlite>
.table
というコマンドを入力することでテーブルの一覧を見ることができます。
migrateコマンドで作成したusersテーブルとpassword_resetsテーブルが存在することを確認してください。
(migrateを実行した際に、マイグレーションの履歴を保持するためのmigrationsというテーブルも自動的に作成されています)sqlite> .table migrations password_resets users
以上を確認できたらSQLite3を終了させます。
SQLite3を終了させるにはCtrl+D
を入力します。ToDoテーブルの作成
機能を開発する前に、データを格納するtodosテーブルをデータベースに作成しておきます。
マイグレーションファイルの作成
"laravel_todolist"ディレクトリで以下のコマンドを実行します。
php artisan make:migration create_todos_table --create=todosマイグレーションを実行してtodosテーブルを作成
"laravel_todolist/database/migrations"ディレクトリに
YYYY_MM_DD_hhmmss_create_todo_table.php
といったフォーマットの名前でファイルが作成されます。これがマイグレーションファイルです。
このファイルをviで開きます。YYYY_MM_DD_hhmmss_create_todo_table.php<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateTodoTable extends Migration /** * Run the migrations. * * @return void */ public function up() { Schema::create('todos', function (Blueprint $table) { $table->increments('id'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('todos'); } }upファンクションに定義されているのが、マイグレーションが実行された際にデータベースに作成されるtodosテーブルの情報です。todosテーブルには以下の情報を加えたいと思います。
カラムの論理名 カラムの物理名 id id タスクの名前 task_name タスクの説明 task_description 担当者の名前 assign_person_name 見積時間(h) estimate_hour upファンクションを以下のように書き換えます。
YYYY_MM_DD_hhmmss_create_todo_table.php<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateTodoTable extends Migration /** * Run the migrations. * * @return void */ public function up() { Schema::create('todos', function (Blueprint $table) { $table->increments('id'); $table->string('task_name'); // 追加 $table->text('task_description'); // 追加 $table->string('assign_person_name'); // 追加 $table->string('estimate_hour'); // 追加 $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('todos'); } }修正が終わったら、ファイルを保存します。
"laravel_todolist"ディレクトリに戻り、以下のコマンドでマイグレーションを実行してください。php artisan migrateSQLite3でdatabase.sqliteを開き、todosテーブルが作成されていることを確認します。
sqlite3 database/database.sqlite SQLite version 3.28.0 2019-04-15 14:49:49 Enter ".help" for usage hints. sqlite> .table migrations password_resets todos users sqlite>SQLite3で初期データを登録
引き続き、SQLite3で初期データを登録します。
以下のSQLを実行します。sqlite> INSERT INTO todos(task_name, task_description, assign_person_name, estimate_hour) VALUES("テスト", "テストです", "テストさん", "1");以下のSQLを実行して、初期データが登録されたことを確認してください。
sqlite> SELECT * FROM todos; 1|テスト|テストです|テストさん|1||Todoモデルクラスを実装
todosテーブルをLaravelから操作するために、Todoモデルクラスを実装します。
"laravel_todolist"ディレクトリで以下のコマンドを実行して、Todoモデルクラスを生成します。
php artisan make:model Todo"laravel_todolist/app/Todo.php"というファイルが生成されるので、vi等で開き以下の行を追加して保存します。
ID列を$guardedで指定したので、ID列にはデータの作成時や更新時に値が入らないようになります。(ID列には、データ作成時に自動で連番を入れたいのでこのような指定を行っておきます)Todo.php<?php namespace App; use Illuminate\Database\Eloquent\Model; class Todo extends Model { protected $guarded = ['id']; // 追加 }タスク一覧ページを実装
CRUDのR(Read)に該当する機能を実装します。
ルーティングの設定
"laravel_todolist/routes/web.php"をvi等で開きます。
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'); });
/
へのルーティングが予め書かれていますが、ここを以下のように書き換えます。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('/', 'TodolistFormController@index');これで、
/
を呼び出されたときに、TodolistFormControllerクラスのindexファンクションが実行される指定になりました。コントローラの作成
web.phpを書き換えましたが、まだTodolistFormControllerクラスが無いので実装しましょう。
"laravel_todolist"ディレクトリで以下のコマンドを実行してTodolistFormControllerクラスを作成します。
php artisan make:controller TodolistFormController"laravel_todolist/app/Http/Controllers/TodolistFormController.php"というファイルが作成されるので、このファイルをvi等で開きます。
TodolistFormController.php<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class TodolistFormController extends Controller { // }まだ何もファンクションが無い状態なので、ここにタスク一覧を表示するindexファンクションを追加します。
TodolistFormController.php<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\ToDo; // 追加 class TodolistFormController extends Controller { // タスクを一覧で表示 public function index() { $todos = Todo::orderBy('id', 'asc')->get(); return view('todo_list', [ "todos" => $todos ]); } }
use App\ToDo;
でTodoモデルクラスを読み込んでいることに注意してください。
ToDoモデルクラスのget()
ファンクションを呼び出すことで、todosテーブルの値を取り出すことが出来ます。
また、orderBy()
ファンクションを使用することでデータのソートも行っています。returnでは"todo_list"ビューを使用し、
$todos
データを渡すように定義しています。
次は"todo_list"ビューを作成しましょう。ビューの作成
ビューファイルはartisanコマンドで作成できないので、touchコマンド等で直接ファイルを作成します。
"laravel_todolist/resources/views/"ディレクトリに"todo_list.blade.php"というファイルを作成し、vi等で開いて以下のように実装します。todo_list.blade.php<h1>ToDo List</h1> <div> <h2>タスクの一覧</h2> <a href="/create-page">タスクを追加</a> <table border="1"> <tr> <th>タスクの名前</th> <th>タスクの説明</th> <th>担当者の名前</th> <th>見積時間(h)</th> <th colspan="2">操作</th> </tr> @foreach($todos as $todo) <tr> <td>{{$todo->task_name}}</td> <td>{{$todo->task_description}}</td> <td>{{$todo->assign_person_name}}</td> <td>{{$todo->estimate_hour}}</td> <td><a href="/edit-page/{{$todo->id}}">編集</a></td> <td><a href="/delete-page/{{$todo->id}}">削除</a></td> </tr> @endforeach </table> </div>見るべきポイントとしては
@foreach($todos as $todo)
でタスクの数だけ繰り返し表示している箇所です。コントローラからタスクの一覧が$todos
という名前で渡されてくるので、これをforeachでループさせながら全件表示しています。また、まだタスクの追加・編集・削除機能は実装していませんが、
<a>
タグによるリンクは先に実装してしまいます。
編集と削除のURLはどのタスクを対象とするのかをIDで判別するため、URLに{{$todo->id}}
を渡しています。動作確認
"laravel_todolist"ディレクトリで以下のコマンドを実行し、Laravelの開発サーバを起動します。
php artisan serveブラウザで
http://localhost:8000/
を開き、SQLiteで登録しておいた初期データが表示されることを確認してください。これで、タスク一覧ページの実装が出来ました。
試しに、SQLite3でINSERTのSQLを実行して初期データの件数を増やすと、タスク一覧ページに表示されるタスクの件数も増えていくはずです。
次は、タスクを追加するページを実装します。タスク追加ページを実装
CRUDのC(Create)に該当する機能を実装します。
ルーティングの設定
"laravel_todolist/routes/web.php"を編集します。
タスク追加ページを表示する/create-page
と、追加ボタンが押されたら実際にタスクを追加する/create
へのルーティングを追加します。
/create-page
はGETメソッドの指定で、/create
はPOSTメソッドの指定になる点に注意してください。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('/', 'TodolistFormController@index'); Route::get('/create-page', 'TodolistFormController@createPage'); Route::post('/create', 'TodolistFormController@create');コントローラの作成
"laravel_todolist/app/Http/Controllers/TodolistFormController.php"を開き、TodolistFormControllerの実装に処理を追加します。
createPageファンクションの追加
"createPage"ファンクションはタスク作成ページを返すだけでよいので、"todo_create"ビューを返しているだけです。
TodolistFormController.php// ここより上は省略 // タスク作成画面を表示 public function createPage() { return view('todo_create'); } // 以下省略createファンクションの追加
"create"ファンクションはタスク作成ページで「作成」ボタンを押されたら実行されます。タスク作成ページで入力された情報がPOSTリクエストのパラメータとして渡ってくるので、その情報をもとに新しいタスクを作成しています。
Todoモデルクラスをnewしてインスタンスを生成し、タスクの情報をパラメータに詰めて
save()
ファンクションを呼び出すことでtodosテーブルにレコードが保存されます。処理の最後で
return redirect('/')
することで、タスク登録後に/
ページへリダイレクトされます。TodolistFormController.php// ここより上は省略 // タスクを登録 public function create(Request $request) { $todo = new Todo(); $todo->task_name = $request->task_name; $todo->task_description = $request->task_description; $todo->assign_person_name = $request->assign_person_name; $todo->estimate_hour = $request->estimate_hour; $todo->save(); return redirect('/'); } // 以下省略ビューの作成
タスク追加画面が必要なので作成します。
"laravel_todolist/resources/views/"ディレクトリに"todo_create.blade.php"というファイルを作成し、vi等で開いて以下のように実装します。todo_create.blade.php<h1>ToDo List</h1> <div> <h2>タスクを追加</h2> <form method="POST" action="/create"> @csrf <p> タスクの名前:<input type="text" name="task_name"> </p> <p> タスクの説明:<input type="text" name="task_description"> </p> <p> 担当者の名前:<input type="text" name="assign_person_name"> </p> <p> 見積時間(h):<input type="number" name="estimate_hour"> </p> <input type="submit" name="create" value="追加"> </form> <a href="/">戻る</a> </div>
<form method="POST" action="/create">
としてあるので、<input type="submit" name="create" value="追加">
ボタンが押されたら/create
のPOSTメソッドへリクエストが飛ぶことになります。動作確認
"laravel_todolist"ディレクトリで
php artisan serve
コマンドでテストサーバを起動し、以下の手順で新しいタスクを追加できることを確認します。タスク編集ページを実装
CRUDのU(Update)に該当する機能を実装します。
ルーティングの設定
"laravel_todolist/routes/web.php"を編集します。
タスク編集ページを表示する/edit-page
と、修正ボタンが押されたら実際にタスクを更新する/edit
へのルーティングを追加します。
/edit-page
はGETメソッドの指定で、/edit
はPOSTメソッドの指定になる点はタスク作成ページと同様です。
ただし/edit-page
へのルーティングは、「どのタスクを編集するのか」という情報をURLパラメータに加えたいため、'/edit-page/{id}'
という指定になっている点に注意です。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('/', 'TodolistFormController@index'); Route::get('/create-page', 'TodolistFormController@createPage'); Route::post('/create', 'TodolistFormController@create'); Route::get('/edit-page/{id}', 'TodolistFormController@editPage'); Route::post('/edit', 'TodolistFormController@edit');コントローラの作成
"laravel_todolist/app/Http/Controllers/TodolistFormController.php"を開き、TodolistFormControllerの実装に処理を追加します。
editPageファンクションの追加
"editPage"ファンクションは、編集対象のタスクのIDがURLパラメータの
$id
で渡ってくるので、$id
を引数として受け付けます。
Todoモデルクラスのfindファンクションを用いるとテーブルのキーを用いた検索ができるので、引数で受け取った$id
を使って対象のタスクを取得し、"todo_edit"ビューと合わせて返しています。TodolistFormController.php// ここより上は省略 // タスク編集画面を表示 public function editPage($id) { $todo = Todo::find($id); return view('todo_edit', [ "todo" => $todo ]); } // 以下省略editファンクションの追加
"edit"ファンクションはタスク編集ページで「修正」ボタンを押されたら実行されます。タスク修正ページで入力された情報がPOSTリクエストのパラメータとして渡ってくるので、その情報をもとにタスクの情報を更新しています。
まず、Todoモデルクラスのfindファンクションを使って、リクエストパラメータに入っている
$id
のタスクをデータベースのtodosテーブルから取得します。
そして、updateファンクションを使って、取得したタスクの内容を書き換えます。処理の最後で
return redirect('/')
することで、タスク編集後に/
ページへリダイレクトされます。TodolistFormController.php// ここより上は省略 // タスクを更新 public function edit(Request $request) { Todo::find($request->id)->update([ 'task_name' => $request->task_name, 'task_description' => $request->task_description, 'assign_person_name' => $request->assign_person_name, 'estimate_hour' => $request->estimate_hour ]); return redirect('/'); } // 以下省略ビューの作成
タスク編集画面が必要なので作成します。
"laravel_todolist/resources/views/"ディレクトリに"todo_edit.blade.php"というファイルを作成し、vi等で開いて以下のように実装します。todo_edit.blade.php<h1>ToDo List</h1> <div> <h2>タスクの修正</h2> <form method="POST" action="/edit"> @csrf <input type="hidden" name="id" value="{{$todo->id}}"> <p> タスクの名前:<input type="text" name="task_name" value="{{$todo->task_name}}"> </p> <p> タスクの説明:<input type="text" name="task_description" value="{{$todo->task_description}}"> </p> <p> 担当者の名前:<input type="text" name="assign_person_name" value="{{$todo->assign_person_name}}"> </p> <p> 見積時間(h) :<input type="number" name="estimate_hour" value="{{$todo->estimate_hour}}"> </p> <input type="submit" name="edit" value="修正"> </form> <a href="/">戻る</a> </div>
<form method="POST" action="/edit">
としてあるので、<input type="submit" name="edit" value="修正">
ボタンが押されたら/edit
のPOSTメソッドへリクエストが飛ぶことになります。
また、各inputのvalueには、コントローラから渡された$todo
データに格納されているタスクの情報が入ります。これによって、タスク編集画面を開いたときに、タスクの情報が予めフォームに入力された状態になります。動作確認
"laravel_todolist"ディレクトリで
php artisan serve
コマンドでテストサーバを起動し、以下の手順でタスクの編集ができることを確認します。タスク削除ページを実装
CRUDのD(Delete)に該当する機能を実装します。
ルーティングの設定
"laravel_todolist/routes/web.php"を編集します。
タスク削除ページを表示する/delete-page
と、修正ボタンが押されたら実際にタスクを更新する/delete
へのルーティングを追加します。
/delete-page
はGETメソッドの指定で、/delete
はPOSTメソッドの指定になる点はタスク編集ページと同様です。/delete-page
へのルーティングについても、「どのタスクを削除するのか」という情報をURLパラメータに加えたいため、'/delete-page/{id}'
という指定になっています。
ただし、/delete
はPOSTメソッドの指定で'/delete/{id}'
となっている点は注意です。これは、削除ページではタスクの内容を確認するだけでフォームへの入力は行わないため、リクエストパラメータを使うよりもURLパラメータにIDを仕込んだほうがシンプルだと考えたためです。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('/', 'TodolistFormController@index'); Route::get('/create-page', 'TodolistFormController@createPage'); Route::post('/create', 'TodolistFormController@create'); Route::get('/edit-page/{id}', 'TodolistFormController@editPage'); Route::post('/edit', 'TodolistFormController@edit'); Route::get('/delete-page/{id}', 'TodolistFormController@deletePage'); Route::post('/delete/{id}', 'TodolistFormController@delete');コントローラの作成
"laravel_todolist/app/Http/Controllers/TodolistFormController.php"を開き、TodolistFormControllerの実装に処理を追加します。
deletePageファンクションの追加
"deletePage"ファンクションは、削除対象のタスクのIDがURLパラメータの
$id
で渡ってくるので、$id
を引数として受け付けます。
Todoモデルクラスのfindファンクションで対象のタスクを取得し、"todo_delete"ビューと合わせて返しています。TodolistFormController.php// ここより上は省略 // タスク削除画面を表示 public function deletePage($id) { $todo = Todo::find($id); return view('todo_delete', [ "todo" => $todo ]); } // 以下省略deleteファンクションの追加
"delete"ファンクションはタスク削除ページで「削除」ボタンを押されたら実行されます。選択されたタスクのIDがURLパラメータとして渡ってくるので、その情報をもとにタスクの情報を削除しています。
まず、Todoモデルクラスのfindファンクションを使って、URLパラメータに入っている
$id
のタスクをデータベースのtodosテーブルから取得します。
そして、deleteファンクションを使って、取得したタスクをtodosテーブルから削除しています。処理の最後で
return redirect('/')
することで、タスク編集後に/
ページへリダイレクトされます。TodolistFormController.php// ここより上は省略 // タスクを削除 public function delete(Request $id) { Todo::find($id)->delete(); return redirect('/'); } // 以下省略ビューの作成
"laravel_todolist/resources/views/"ディレクトリに"todo_delete.blade.php"というファイルを作成し、vi等で開いて以下のように実装します。
todo_delete.blade.php<h1>ToDo List</h1> <div> <h2>タスクを削除</h2> <form method="POST" action="/delete/{{$todo->id}}"> @csrf <p> タスクの名前:{{$todo->task_name}} </p> <p> タスクの説明:{{$todo->task_description}} </p> <p> 担当者の名前:{{$todo->assign_person_name}} </p> <p> 見積時間(h) :{{$todo->estimate_hour}} <p> <input type="submit" name="delete" value="削除"> </form> <a href="/">戻る</a> </div>
<form method="POST" action="/delete">
としてあるので、<input type="submit" name="delete" value="削除">
ボタンが押されたら/delete
のPOSTメソッドへリクエストが飛ぶことになります。
削除対象のタスクの情報はコントローラから$todo
という名前のデータで渡されてきているので、$todo
から値を取り出してHTML上に直接(inputタグを使わずに)表示しています。動作確認
"laravel_todolist"ディレクトリで
php artisan serve
コマンドでテストサーバを起動し、以下の手順でタスクの削除ができることを確認します。その他の機能を実装
以上で、超シンプルなToDoアプリのCRUD(Create, Read, Update, Delete)の機能が実現できました。
このままでもまぁまぁ使えるのですが、もう少し完成度を上げるために以下の2点の改善を行います。
- タスクに設定した見積時間の合計を表示する
- Validationの実装(タスク作成時と編集時に入力内容をチェックする)
見積時間の合計を表示
個々のタスクの見積時間はタスク一覧ページで表示されますが、タスクの数が増えてくるといちいち合計を手計算しなければいけないのは面倒です。
そこで、全てのタスクの見積時間を合計し、タスク一覧ページに表示しましょう。コントローラの修正
"laravel_todolist/app/Http/Controllers/TodolistFormController.php"を開き、TodolistFormControllerのindexファンクションに処理を追加します。
TodolistFormController.php// ここより上は省略 // タスクを一覧で表示 public function index() { $todos = Todo::orderBy('id', 'asc')->get(); // 見積時間の合計を求める $estimate_hour_sum = 0; foreach ($todos as $todo){ $estimate_hour_sum += $todo->estimate_hour; } return view('todo_list', [ "todos" => $todos, "estimate_hour_sum" => $estimate_hour_sum ]); } // 以下省略新たに
$estimate_hour_sum
という変数を定義し、foreachで全タスクを回してestimate_hour
を合算します。そして最後に、ビューを返す際のデータにestimate_hour_sum
も加えてあげます。ビューの修正
"laravel_todolist/resources/views/todo_list.blade.php"を開いて以下のように修正します。
todo_list.blade.php<h1>ToDo List</h1> <div> <h2>タスクの一覧</h2> <a href="/create-page">タスクを追加</a> <table border="1"> <tr> <th>タスクの名前</th> <th>タスクの説明</th> <th>担当者の名前</th> <th>見積時間(h)</th> <th colspan="2">操作</th> </tr> @foreach($todos as $todo) <tr> <td>{{$todo->task_name}}</td> <td>{{$todo->task_description}}</td> <td>{{$todo->assign_person_name}}</td> <td>{{$todo->estimate_hour}}</td> <td><a href="/edit-page/{{$todo->id}}">編集</a></td> <td><a href="/delete-page/{{$todo->id}}">削除</a></td> </tr> @endforeach </table> <p> 見積時間の合計(h):{{$estimate_hour_sum}} </p> </div>一番下に見積時間の合計を追加しています。
Validationの実装
現状では、タスク作成やタスク編集で、フォームの値が空の状態だった場合、QueryExceptionが発生してしまいます。
これは、todosテーブルの各カラムについてNULLを許容していないためですが、ユーザがタスク作成ページやタスク編集ページではフォームの値を埋めた状態でボタンを押してもらえればこのExceptionは回避できます。
そのような機能を実現させたい場合、Validationを実装することでフォームに入力されたデータを先にチェックし、異常なデータが有る場合は処理を中断させることができます。コントローラの修正
"laravel_todolist/app/Http/Controllers/TodolistFormController.php"を開き、TodolistFormControllerのcreateファンクションに処理を追加します。
TodolistFormController.php// ここより上は省略 // タスクを登録 public function create(Request $request) { $validator = $request->validate([ 'task_name' => 'required', 'task_description' => 'required', 'assign_person_name' => 'required', 'estimate_hour' => 'required' ]); $todo = new Todo(); $todo->task_name = $request->task_name; $todo->task_description = $request->task_description; $todo->assign_person_name = $request->assign_person_name; $todo->estimate_hour = $request->estimate_hour; $todo->save(); return redirect('/'); } // 以下省略Todoモデルクラスをnewする前に、HTTPリクエストで渡されてきたパラメータを
$validator
を使ってチェックしています。
チェックのルールは各パラメータで個別に設定することができ、今回の場合は全てのパラメータについてrequired
を指定して必須としています。Validationルールは他にも色々あるので、以下の公式ガイドの「使用可能なバリデーションルール」を参照してみてください。
Laravel 5.7 バリデーションビューの修正
"laravel_todolist/resources/views/todo_create.blade.php"を開いて以下のように修正します。
todo_create.blade.php<h1>ToDo List</h1> <div> <h2>タスクを追加</h2> <form method="POST" action="/create"> @csrf @if ($errors->any()) <ul> @foreach ($errors->all() as $error) <li style="color:red">{{$error}}</li> @endforeach </ul> @endif <p> タスクの名前:<input type="text" name="task_name"> </p> <p> タスクの説明:<input type="text" name="task_description"> </p> <p> 担当者の名前:<input type="text" name="assign_person_name"> </p> <p> 見積時間(h):<input type="number" name="estimate_hour"> </p> <input type="submit" name="create" value="追加"> </form> <a href="/">戻る</a> </div>
@if ($errors->any())
で、Validationエラーがあった場合のエラーメッセージ表示領域を定義しています。
コントローラ側で$validator
のチェックルールに引っかかった場合、自動的にビューが再表示されます。その際に$errors
にエラー内容が配列で格納されているので、foreachループで回して全てのエラーメッセージを<li>
タグでリスト表示しています。日本語メッセージの表示
日本語メッセージファイルのインストール
ここまででもValidationチェックとエラーメッセージの表示はできるのですが、エラーメッセージが英文表記となっていてユーザフレンドリーとは言い難い状態です。
そのため、まずは公式の手順に沿って日本語メッセージのインストールを行います。Laravel 5.7 validation.php言語ファイル
"laravel_todolist"ディレクトリで以下のコマンドを実行します。
php -r "copy('https://readouble.com/laravel/5.7/ja/install-ja-lang-files.php', 'install-ja-lang.php');" php -f install-ja-lang.php php -r "unlink('install-ja-lang.php');""laravel_todolist/resources/lang/ja"というディレクトリが新たに作られ、その配下にあるvalidation.phpがValidationの日本語メッセージです。
Laravelプロジェクトの言語設定を変更
"laravel_todolist/config/app.php"を開き、
locale
というキーの値をen
からja
に変更して保存します。app.php// ここより上は省略 /* |-------------------------------------------------------------------------- | Application Locale Configuration |-------------------------------------------------------------------------- | | The application locale determines the default locale that will be used | by the translation service provider. You are free to set this value | to any of the locales which will be supported by the application. | */ 'locale' => 'ja', // 以下省略カスタムバリデーション属性名の設定
ここまででメッセージのフォーム名以外は日本語になりますが、まだフォーム名はリクエストパラメータのキー名がそのまま表示されてしまいます。ここを日本語名に変換するには、カスタムバリデーション属性名を設定する必要があります。
"laravel_todolist/resources/lang/ja/validation.php"を開いて以下のように修正します。validation.php// ここより上は省略 /* |-------------------------------------------------------------------------- | カスタムバリデーション属性名 |-------------------------------------------------------------------------- | | 以下の言語行は、例えば"email"の代わりに「メールアドレス」のように、 | 読み手にフレンドリーな表現でプレースホルダーを置き換えるために指定する | 言語行です。これはメッセージをよりきれいに表示するために役に立ちます。 | */ 'attributes' => [ 'task_name' => 'タスクの名前', 'task_description' => 'タスクの説明', 'assign_person_name' => '担当者の名前', 'estimate_hour' => '見積時間', ], ];動作確認
タスク追加画面でフォームの値を入力せずに追加ボタンを押すと、以下のようにValidationメッセージが表示されるようになります。
同様のExceptionはタスク編集画面でも起こりうるので、タスク編集画面とコントローラのeditファンクションにもValidationを導入しておくと良いでしょう。
ToDoアプリの機能追加例
超シンプルなToDoアプリの作成紹介は以上となります。
ここからは、各々がToDoアプリをベースにしながら付帯機能の開発を進めて、Laravelによるアプリ開発の理解を深めるのがよいと思います。
いくつか機能追加の例を示しておきます。見積時間(h)のデータ型を文字型から数値型に変える
現状では見積時間(h)はマイグレーションファイルでStringとして定義されているため、todosテーブル上でも文字型として定義されています。
しかし、時間は数字であったほうが集計等で扱いやすいので、文字型ではなく数字型の方が良いはずです。マイグレーションファイルを追加し、estimate_hourの定義を数値型に変更する対応をしてみると、開発途中でデータ型を変更する練習になると思います。
担当者の名前をセレクトボックスから選択できるようにする
現状では担当者の名前はテキストボックスに直接入力していますが、ここは予め決められた名前の中から選択できる方が使い勝手が良いはずです。
担当者の名前をセレクトボックスから選択できるようにするには、ざっと考えただけでも以下のような対応が必要になってきそうです。
- 担当者の名前をデータベースに登録しておきたい
- 担当者の名前のデータベースが必要
- 担当者の名前のCRUD機能が必要
- タスク登録時に担当者の名前をデータベースから取得したい
- タスクをデータベースに保存する際の担当者の名前は、Valueではなくidを保存する
- タスク編集時に担当者の名前をデータベースから取得したい
- タスク登録時と似ているが、セレクトボックスには予めidに対応するvalueを選択した状態にしておきたい
- その他考慮すべきこと
- 担当者の名前を削除するときに、その担当者のタスクが残っている場合にどうするか?
これは、ToDoモデルに加えて、新たに担当者のモデル(AssignPersonモデル)が必要となり、モデル間の関係も考慮する練習になると思います。
ログイン機能の実装
現状ではこのToDoアプリはログイン機能を備えていないため、URLを知っていれば誰でもアクセスしてデータを操作できてしまいます。これではセキュリティ面で実用的では無いため、ログイン機能を備えるべきでしょう。
Laravelは標準でAuthの機能を備えているので、公式ガイドを参考にしながらログイン機能を追加してみるとより実用的なアプリになると思います。
Laravel 5.7 認証また、ユーザに対して「管理者」「一般」といった権限レベルを追加し、タスクの削除ができるのは管理者のみといった権限設定を行えるようにするのも学習になると思います。
排他制御の実装
ログイン機能を追加したら、実用性を上げるためにデータ更新時の排他制御も実現させたいところです。
現状では、複数のアカウントから同時にタスクの編集を行われた場合、一方の編集内容は破棄されてしまうという問題があります。
処理の流れを示すと以下のようになります。排他制御が行われていない場合の処理の流れ
- Aさんがタスク1のタスク編集画面を開く
- Bさんがタスク1のタスク編集画面を開く
- Aさんがタスク1の内容を書き換えて保存する
- Bさんがタスク1の内容を書き換えて保存する ※Aさんの編集内容はここには反映されていない
- タスク1はBさんの編集内容で保存されるため、Aさんの編集内容は破棄されてしまう
このような問題を回避するためには、排他制御としてデータ更新時の楽観ロック(更新前に読み込んだデータが、他のユーザによって先に更新されていないかチェックする仕組み)を実装する必要があります。
排他制御を実装している場合、処理の流れは以下のようになります。排他制御が行われる場合の処理の流れ
- Aさんがタスク1のタスク編集画面を開く
- Bさんがタスク1のタスク編集画面を開く
- Aさんがタスク1の内容を書き換えて保存しようとしたときに、他の人が先に書き換え・保存していないかチェックする
- 他の人によって書き換え・保存されていないので、Aさんの書き換え内容は保存される
- Bさんがタスク1の内容を書き換えて保存しようとしたときに、他の人が先に書き換え・保存していないかチェックする
- Aさんによって書き換え・保存されているので、Bさんの書き換えは失敗する
複数アカウントでデータが操作されるアプリであれば排他制御は避けて通れないので、良い練習になると思います
- 投稿日:2020-10-20T09:48:40+09:00
PHP 7(.4)のDockerコンテナ(fpm-alpine)にphp-mecabを導入してPHPでMeCabを使う
DockerのPHPイメージにMeCabとmecab-ipadic-NEologdを導入して、PHPから使えるように設定する方法を紹介します?
PHPフレームワークやWebサーバーを導入せず、PHPでMeCabを使えるかどうかまで確認します。ここで導入できたら必要なものを追加しましょう✨
PHPのイメージは次を用いて確認しています。
DockerfileFROM php:7.4.12-fpm-alpinedocker-composeの設定
起動・終了を簡単にするためdocker-composeを用います。次の
docker-compose.yml
をプロジェクトのルートに作成します。
- PHPコンテナにMeCabをインストールしてカスタムでコンテナを作るため、新規作成するDockerfileの場所を
build
のdockerfile
で指定しますvolumes
にはPHPでMeCabを使うサンプルスクリプトを配置するホスト側のディレクトリとコンテナ側のディレクトリをマウントします- PHPのコンテナを起動させ続けるために
tty
をtrueにしておきますdocker-compose.ymlversion: '3' services: php-mecab: build: context: ./ dockerfile: Dockerfile volumes: - ./src:/app tty: trueMeCabとmecab-ipadic-NEologdを導入する
同じくプロジェクトのルートに
Dockerfile
を作成して、PHPのコンテナにMeCabとmecab-ipadic-NEologdを導入します。古い記事ですが、PHP7.4.12の現在もこちらの記事と同じ方法で構築できます。
https://qiita.com/nownabe/items/4171776aec1f05de9f28DockerfileFROM php:7.4.12-fpm-alpine ## ここから # https://qiita.com/nownabe/items/4171776aec1f05de9f28#dockerfile RUN apk add --update --no-cache build-base ENV MECAB_VERSION 0.996 ENV IPADIC_VERSION 2.7.0-20070801 ENV mecab_url https://drive.google.com/uc?export=download&id=0B4y35FiV1wh7cENtOXlicTFaRUE ENV ipadic_url https://drive.google.com/uc?export=download&id=0B4y35FiV1wh7MWVlSDBCSXZMTXM ENV build_deps 'curl git bash file sudo openssh' ENV dependencies 'openssl' RUN apk add --update --no-cache ${build_deps} \ # Install dependencies && apk add --update --no-cache ${dependencies} \ # Install MeCab && curl -SL -o mecab-${MECAB_VERSION}.tar.gz ${mecab_url} \ && tar zxf mecab-${MECAB_VERSION}.tar.gz \ && cd mecab-${MECAB_VERSION} \ && ./configure --enable-utf8-only --with-charset=utf8 \ && make \ && make install \ && cd \ # Install IPA dic && curl -SL -o mecab-ipadic-${IPADIC_VERSION}.tar.gz ${ipadic_url} \ && tar zxf mecab-ipadic-${IPADIC_VERSION}.tar.gz \ && cd mecab-ipadic-${IPADIC_VERSION} \ && ./configure --with-charset=utf8 \ && make \ && make install \ && cd \ # Install Neologd && git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git \ && mecab-ipadic-neologd/bin/install-mecab-ipadic-neologd -n -y \ # Clean up && apk del ${build_deps} \ && rm -rf \ mecab-${MECAB_VERSION}* \ mecab-${IPADIC_VERSION}* \ mecab-ipadic-neologd ## ここまでphp-mecabを導入する
MeCabをPHPで使う場合はphp-mecabモジュールを導入する必要があります。Dockerfileを作るポイントとしては次のとおりです。
autoconf
を導入しないとphp-mecabのビルドに必要なconfigure
が生成されないphp-config
,mecab-config
は/usr/local/bin/
以下に配置されているこのポイントを踏まえてphp-mecabを導入するため Dockerfileに追記すると 次のようになります。
Dockerfile# ... ## ここまで ## ここから追記する ENV build_deps_phpmecab 'git autoconf' COPY php.mecab.ini /usr/local/etc/php/conf.d/ RUN apk add --update --no-cache ${build_deps_phpmecab} \ && git clone https://github.com/rsky/php-mecab.git \ && cd ./php-mecab/mecab \ && phpize \ && ./configure --with-php-config=/usr/local/bin/php-config --with-mecab=/usr/local/bin/mecab-config \ && make \ && make test \ && make install \ # Clean up && cd \ && rm -rf php-mecab \ && apk del ${build_deps_phpmecab} # ▼▼このコマンドが最後に来るようにしてください RUN docker-php-ext-install pdo pdo_mysql
php.mecab.ini
をCOPYしていますが、次の手順でこのファイルを作成します。PHPからMeCabモジュールを読み込む
導入したphp-mecabのモジュールをPHPで有効にするため、PHPの設定ファイル(
php.mecab.ini
)を作成してmecab.so
を指定します。php.mecab.iniextension=mecab.so最終的には次のファイル構成になります。
$ tree . ├── Dockerfile ├── docker-compose.yml └── php.mecab.iniPHPでMeCabを使う
PHP7向けに
MeCab_Tagger
から\MeCab\Tagger
に変更されています。 バックスラッシュもそのまま使います。sample_php_mecab.php<?php $str = 'センテンススプリングでも文春でもどっちでもいいだろ'; $options = array('-d', '/usr/local/lib/mecab/dic/mecab-ipadic-neologd'); $mecab = new \MeCab\Tagger($options); $nodes = $mecab->parseToNode($str); echo $str . "\n"; foreach ($nodes as $n) { $stat = $n->getStat(); // 単語かどうか if ($stat != 0) { continue; } $features = explode(',', $n->getFeature()); if ($features[0] !== '名詞') { continue; } // 単語 echo "Surface: " . $n->getSurface() . "\n"; }docker-compose up --build -d php-mecab docker-compose exec php-mecab /bin/sh php /app/sample_php_mecab.phpこれでMeCabが使えるPHPのコンテナが作成できます。
この構成ではWebブラウザで表示して確認することはできないので、
- Laravelなどのフレームワークを導入して開発用サーバーを立ち上げる
- 別途NginxなどのWebサーバーのコンテナを導入して、このPHPコンテナで動作しているFPMと接続させる
などの作業は必要です。
参考
Alpine LinuxでMeCab with NEologd - Qiita
https://qiita.com/nownabe/items/4171776aec1f05de9f28phpでmecab - Qiita
https://qiita.com/kojirock5260/items/1787d70b9cfca3e43d12phpizeでエラー : てるてる坊主
http://teru2-bo2.blogspot.com/2012/05/phpize.htmlPHP7.0+mecabでハマった事 - Qiita
https://qiita.com/murota/items/3c76cc3ca0fd819dbac7
- 投稿日:2020-10-20T09:29:03+09:00
Laravel8 CRUD処理を使った投稿アプリを作成する その4 投稿一覧ページの作成
目的
- 投稿された内容の一覧を表示するページの作成を行う
実施環境
- 筆者の実施環境を記載する。
- ハードウェア環境
項目 情報 OS macOS Catalina(10.15.5) ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports) プロセッサ 2 GHz クアッドコアIntel Core i5 メモリ 32 GB 3733 MHz LPDDR4 グラフィックス Intel Iris Plus Graphics 1536 MB
- ソフトウェア環境
項目 情報 備考 PHP バージョン 7.4.8 Homebrewを用いてこちらの方法で導入→Mac HomebrewでPHPをインストールする Laravel バージョン 8.6.0 commposerを用いてこちらの方法で導入→Mac Laravelの環境構築を行う MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いてこちらの方法で導入→Mac HomebrewでMySQLをインストールする 前提条件
- その1の記事の内容が完了していること。
前提情報
- 作成するアプリ名は「laravel8_crud」とする。
- 作成するMySQLのデータベース名は「laravel8_crud_DB」とする。
- 下記に今回の作業のあとのソースコードのリモートリポジトリのリンクを記載する。
- 投稿された内容の一覧ページのURLはhttp://127.0.0.1:8000/outputとする。
概要
- ルーティング情報の記載
- コントローラファイルの記載
- ビューファイルの作成と記載
- 確認
詳細
ルーティングの記載
laravel8_crudディレクトリで下記コマンドを実行してルーティングファイルを開く。
$ vi routes/web.php開いたファイルに下記の行を追記する。
laravel8_crud/routes/web.phpRoute::get('/output', [ContentController::class, 'output'])->name('output');追記後のルーティングファイルの内容を下記に記載する。
laravel8_crud/routes/web.php<?php use Illuminate\Support\Facades\Route; use App\Http\Controllers\ContentController; /* |-------------------------------------------------------------------------- | 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'); }); Auth::routes(); Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home'); Route::get('/input', [ContentController::class, 'input'])->name('input'); Route::post('/save', [ContentController::class, 'save'])->name('save'); // 下記を追記する Route::get('/output', [ContentController::class, 'output'])->name('output');コントローラファイルの記載
laravel8_crudディレクトリで下記コマンドを実行して作成したコントローラファイルを開く。
$ vi app/Http/Controllers/ContentController.php下記の内容をクラス内に追記する。
laravel8_crud/app/Http/Controllers/ContentController.phppublic function output() { $contents_get_query = Content::select('*'); $items = $contents_get_query->get(); return view('contents.output', [ 'items' => $items, ]); }すでに記載されている投稿内容を保存するアクション内のリダイレクトの記載を変更する。
laravel8_crud/app/Http/Controllers/ContentController.phppublic function save(Request $request) { $input_content = new Content(); $input_content->content = $request['content']; $input_content->save(); // 下記を修正する return redirect(route('output')); }両内容の追記後のコントローラファイルの内容を下記に記載する。
laravel8_crud/app/Http/Controllers/ContentController.php<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\Content; class ContentController extends Controller { public function input() { return view('contents.input'); } public function save(Request $request) { $input_content = new Content(); $input_content->content = $request['content']; $input_content->save(); // 下記を修正する return redirect(route('output')); } // 下記を追記する public function output() { $contents_get_query = Content::select('*'); $items = $contents_get_query->get(); return view('contents.output', [ 'items' => $items, ]); } // 上記までを追記する }ビューファイルの作成と記載
laravel8_crudディレクトリで下記コマンドを実行してビューファイルを作成する。
vi resources/views/contents/output.blade.php
作成して開いたビューファイルに下記の内容を追記する。
laravel8_crud/resources/views/contents/output.blade.php<h1>output</h1> @foreach ($items as $item) <hr> <p>{{$item['content']}}</p> @endforeach確認
laravel8_crudディレクトリで下記コマンドを実行してローカルサーバを起動する。
$ php artisan serveブラウザで下記にアクセスする。
下記の様に投稿内容が表示されることを確認する。
ブラウザで下記にアクセスする。
テキストボックスに何かしらの文字列を入力して「送信」をクリックする。
下記の画面にリダイレクトし、入力内容が表示されれば本記事の作業は完了である。
- 投稿日:2020-10-20T09:10:07+09:00
Carbon で1週間の日付と曜日を取得する
PHPのCarbonで、日付と曜日(日本語)のリストを取得する方法をシェアします。
実行環境
Laravel Framework 6.18.23
"nesbot/carbon": "2.0"
取得したい結果
array: [ 0 => "10/18 (日)" 1 => "10/19 (月)" 2 => "10/20 (火)" 3 => "10/21 (水)" 4 => "10/22 (木)" 5 => "10/23 (金)" 6 => "10/24 (土)" ]ソースコード
Laravelの
config/app.php
で'locale' => 'ja'
の設定しておく。$numOfDays = 7; //日付を取得する日数 $dt = new Carbon(); $format = 'MM/DD (ddd)'; $week[0] = $dt->today()->isoFormat($format); //Carbonのインスタンスが上書きされないようにcopy()して日付を加算 for ($i=1; $i < $numOfDays ; $i++) { $week[$i] = $dt->copy()->addDay($i)->isoFormat($format); } dump($week);参考
https://carbon.nesbot.com/docs/
https://uiuifree.com/blog/develop/php-carbon-ja-weekday/
- 投稿日:2020-10-20T06:16:41+09:00
Moodle 3.9 マニュアル - バルクユーザーアクション
バルクユーザーアクション
管理>サイト管理>ユーザー>アカウント>一括ユーザーアクションの一括ユーザーアクション機能では、管理者がフィルターを作成してユーザーを選択し、以下のいずれかのアクションを実行することができます。
・電子メールベースの自己登録で作成された、ユーザーによってまだ確認されていないユーザー アカウントの確認
・メッセージを送信する(メッセージングが必要です)
・ユーザーアカウントの削除
・ページにユーザーの一覧を表示する
・ユーザーデータをテキスト、ODS、またはExcelファイル形式でダウンロードできます。
・ユーザーにパスワードを強制的に変更させる
・ユーザーをコホートに追加するバルクユーザーアクションを実行するには
・ユーザーを知っている場合は、利用可能なリストからユーザーを選択するか、「もっと表示」をクリックすると、より多くのフィルターオプションが表示されます。
頻繁に使用されるフィルタは、「デフォルトユーザーフィルタ」(userfiltersdefault) (サイト管理 / ユーザー / アカウント / ユーザー管理) で選択することができます。
・ユーザーは、フルネーム、姓、名、メールアドレス、都市/町、国、確認済み、初回アクセス、最終アクセス、最終ログイン、ユーザー名、認証に基づいてフィルタリングすることができます。(下のスクリーンショット1)
・カスタムプロファイルフィールドを設定している場合は、"プロファイル "ドロップダウンボックスからも利用可能です(以下のスクリーンショット2)。
・リストからユーザーを選択します。
・ドロップダウンメニューからアクションを選択します(下記スクリーンショット3)
・フィルタの追加ボタンをクリックします。
・左側のボックスで必要なユーザーを選択し、右側のボックスに移動します。
・選択したユーザーのドロップダウンボックスから、実行するアクションを選択し、[Go] をクリックします。
(1) 「詳細を表示」をクリックします。
(2)カスタムプロファイルフィールドによるフィルタリング
(3) バルクユーザーアクションの選択
も参照してください。
- 投稿日:2020-10-20T05:58:52+09:00
Moodle 3.9 マニュアル - カテゴリ:登録
カテゴリ:登録
コース内のロール (通常は学生) にユーザーを割り当てるプロセスである登録 (または登録) に関するドキュメントページのインデックス。
カテゴリ「登録」のページ全60ページのうち、以下のページがこのカテゴリに該当します。
B
Banner LMB
C
Capabilities/enrol/category:config
Capabilities/enrol/category:synchronised
Capabilities/enrol/cohort:config
Capabilities/enrol/database:config
Capabilities/enrol/guest:config
Capabilities/enrol/imsenterprise:config
Capabilities/enrol/ldap:manage
Capabilities/enrol/manual:config
Capabilities/enrol/manual:enrol
Capabilities/enrol/manual:manage
Capabilities/enrol/manual:unenrol
Capabilities/enrol/manual:unenrolself
Capabilities/enrol/meta:config
Capabilities/enrol/meta:selectaslinked
Capabilities/enrol/mnet:config
Capabilities/enrol/paypal:config
Capabilities/enrol/paypal:manage
Capabilities/enrol/paypal:unenrol
Capabilities/enrol/paypal:unenrolself
Capabilities/enrol/self:config
Capabilities/enrol/self:holdkey
Capabilities/enrol/self:manage
Capabilities/enrol/self:unenrol
Capabilities/enrol/self:unenrolself
Capabilities/moodle/course:enrolconfig
Capabilities/moodle/course:enrolreview
Category enrolments
Cohort sync
Course enrolment
Course meta linkE
Enrolment FAQ
Enrolment key
Enrolment methods
Enrolment plugins
Development:Enrolment plugins
Development:Enrolment usage overview
Enrolments
error/moodle/disableunenroll
External database enrolmentF
Flat file
G
Guest access
I
IMS Enterprise
K
Keyholder role
L
LDAP enrolment
M
Manual enrolment
Metacourse examples of use
MNet
MNet remote enrolmentsN
Development:New enrolments in 2.0
O
Openlml enrolment plugin
OSS enrolment plugin
Other usersP
Participants
PayPal enrolment
Profile field enrolment
Publish as LTI toolR
Development:Role archetypes
S
Self enrolment
U
Unenrolment
- 投稿日:2020-10-20T05:30:05+09:00
Moodle 3.9 マニュアル - 新規ユーザーの追加
新規ユーザーの追加
内容
1 新規ユーザーの追加
2 一般
2.1 ユーザー名
2.2 認証方法
2.3 一時停止されたアカウント
2.4 パスワードを生成してユーザに通知する
2.5 パスワード
2.6 ファーストネーム
2.7 ラストネーム
2.8 メールアドレス
2.9 電子メールの表示
2.10 電子メールのフォーマット
2.11 メールダイジェストタイプ
2.12 フォーラムの自動購読
2.13 フォーラムのトラッキング
2.14 テキストを編集する場合
2.15 スクリーンリーダー
2.16 市町村
2.17 国
2.18 タイムゾーン
2.19 説明
3 ユーザーの画像
4 関心事
5 オプション1 新規ユーザーの追加
管理者またはマネージャ(またはmoodle/user:createケイパビリティを持つ他のユーザ)は、サイト管理 > ユーザ > アカウント > 新しいユーザの追加で新しいユーザアカウントを作成することができます (ユーザを一括で追加するには、ユーザのアップロードを参照してください)。
26manualreg.png2 一般
2.1 ユーザー名
ユーザはこのユーザ名を使用してMoodleインスタンスにログインします。このユーザ名は一意である必要があります。これは変更することができます。ユーザ名には小文字のアルファベット、数字、ハイフン '-'、アンダースコア '_'、ピリオド '.'、またはアット文字 '@' のみを含めることができます - あなたが「ユーザ名に拡張文字を許可する」を有効にしていない限り (サイト管理 > セキュリティ > サイトポリシー)
2.2 認証方法
この設定は、ユーザの指定したパスワードが正しいかどうかをMoodleがどのようにチェックするかを指定します。
管理者によって作成されたアカウントは手動アカウントメソッドを使用し、ユーザ自身が電子メールサインアップメソッドを使用して作成したアカウントは電子メールベースの自己登録メソッドを使用します。
2.3 一時停止されたアカウント
停止されたユーザーアカウントはログインやウェブサービスの利用ができず、送信メッセージはすべて破棄されます。
2.4 パスワードを生成してユーザーに通知する
Moodleは一時的なパスワードを生成し、ログインおよびパスワードの変更方法をユーザにEメールで通知します。(以下のスクリーンショットのような) 電子メールメッセージは、サイト管理 > 言語 > 言語のカスタマイズで変更することができます。moodle.php」と文字列識別子newusernewpasswordtextを選択してください。
新規ユーザーメール
デフォルトのメッセージを変更する方法2.5 パスワード
これはユーザーのパスワードです。サイトポリシーのパスワードポリシーに従います。ユーザーは、ユーザーメニューの「ユーザー」>「環境設定」からパスワードを変更することができます。
2.6 ファーストネーム
ユーザーのファーストネームです。メッセージ、フォーラムの投稿、参加者リスト、レポートなど、ユーザーに関する情報がページ上に表示される場所では、姓と一緒に表示されます。
2.7 ラストネーム
ユーザーの苗字です。メッセージ、フォーラムの投稿、参加者リスト、レポートなど、ユーザーに関する情報がページ上に表示される場所では、ファーストネームと一緒に表示されます。
2.8 メールアドレス
パスワードリセット通知、フォーラムダイジェスト、その他のメッセージはMoodleサイトからこのメールアドレスに送信されます。
3.0の新機能。サイト管理 > プラグイン > 認証 > 認証の管理で「同じメールアドレスを持つアカウントを許可する」設定が有効になっている場合、メールアドレスの重複を許可することができます。
2.9 電子メールの表示
この設定では、ユーザーのメールアドレスを誰が見ることができるかを制御します。
2.10 電子メールのフォーマット
この設定は、Moodleがユーザにテキストのみのメールを送信するように使用することができます。
2.11 メールダイジェストタイプ
この設定では、購読しているフォーラムの新しい投稿ごとにメールを受信するか、新しい投稿を1日1回ダイジェストで送信するか、どのタイプのダイジェストを受信するかを設定します。
・ダイジェストなし(個別のメールを受信する)。
・コンプリート(1日1回のダイジェスト)または
・お題(投稿トピックのみを含む毎日1回のダイジェスト2.12 フォーラムの自動配信
ユーザーがフォーラムを購読すると、指定した通りに新しい投稿がダイジェストで送信されます。この設定では、ユーザーがフォーラムに自動的に購読するか、各フォーラムの購読ボタンを手動でクリックする必要があるかを設定します。
2.13 フォーラムのトラッキング
この設定では、ユーザーが最後に訪れてから書き込まれた新しい投稿をハイライト表示するかどうかを指定します。
2.14 テキストを編集する場合
この設定は、ユーザーがWYSIWYGテキストエディタを表示するか、単なるテキストボックスを表示するかを指定します。
2.15 スクリーンリーダー
この設定を有効にすると、Moodleの表示が改善され、スクリーンリーダーとの互換性が向上します。
2.16 市町村
ユーザーの市区町村
2.17 国
ユーザーの国
注: 都市と国の既定値は、管理 > サイト管理 > 位置情報 > 位置情報の設定で設定できます。
2.18 タイムゾーン
この設定はメッセージや課題/小テストの締切日の時間をユーザーの現地時間に合わせて調整するために使用します。
2.19 説明
他のユーザーが見ることができるユーザーに関する情報。
3 ユーザーの画像
ユーザの写真は、ユーザがフォーラムなどのMoodleアクティビティに投稿したコンテンツの横にユーザ名の横に表示することができます。詳細はユーザの写真を参照してください。
4 関心事
興味関心のリストは、似たような興味を持つユーザーをつなぐ方法として利用することができます。タグはサイト上で有効にする必要があります。
5 オプション
標準インストールには、いくつかのオプションフィールドがあります。これらには以下が含まれます。Webページ、ICA番号、Skype ID、AIM ID、Yahoo ID、MSN ID、ID番号、機関名、部署名、電話番号、携帯電話番号、住所。
サイト管理者は、より多くのカスタムフィールドを追加したり、これらの「オプション」フィールドをオフにしたりすることができます。
- 投稿日:2020-10-20T04:41:18+09:00
DateTime::formatメソッドに指定するフォーマットを無意味に長くすると,不具合を引き起こしやすい.
TL;DR
index.html
という文字列をフォーマットとして\DateTime::format()
メソッドに引き渡したところ,i801Asia/Tokyox.013108Saturday
という出力が生成された.format()
メソッドに指定するフォーマットは,必要最低限とする.変換されない文字列と誤認して、n
,e
などを指定してしまうと面倒なことになる.- 自分の想定通りに動くか,細かい単位で動作確認・テストを行う.
環境
- PHP7.2
- Ubuntu18.04 LTS
経緯
今日の日付情報を保持した
\DateTime
クラスからディレクトリパス(文字列)をformat()
メソッドを用いて生成し,それをキーとして指定してS3にファイルをアップロードするという処理にて,予期せぬ挙動が発生しました.
DateTime
具体的には,
- 2020/08/index.html
というパスにファイルを自動でアップロードしたかったものが
- 2020/08/i801Asia/Tokyox.013108Saturday
というパスにファイルがアップロードされてしまい,自動生成されるはずの目的のページが生成されないという意図せぬ挙動となりました.
CLIで実行して確認
\DateTime
型のインスタンスを取得し,format()
関数にフォーマットを引き渡して呼び出してやれば,渡したフォーマットに応じた文字列が返ります.php > $date = new \DateTime(); php > echo $date->format('Y') php > ; 2020 php > echo $date->format('m'); 08今回の問題箇所についても実行してみます.
php > echo $date->format('Y/m/index.html'); 2020/08/33801Asia/Tokyox.013108Saturdayああ...
下記のように変更を行い,正常に動作するようになりました.
php > echo $date->format('Y/m/').'index.html'; 2020/08/index.html教訓
format()
メソッドに指定するフォーマットは,必要最低限とする.変換されない文字列だからといってむやみに詰め込まない.- 自分の想定通りに動くか,細かい単位でチェックを行う.
var_dump()
さんありがとう.
- 投稿日:2020-10-20T04:21:12+09:00
【実装編】Laravel 8(+jetstream, fortify)でマルチログイン
はじめに
この記事ではLaravel8でマルチログインの実装をしていきます。
動作環境、詳しい仕様は前回の記事をご覧ください。
前回でUserのログイン機能とAdminモデル、テーブルの作成まで完了しました。
なお、本プロジェクトはGitHubにアップしてありますのでご参考ください。マルチログイン実装
- 一度ルーティングを確認してみる
php artisan route:list
でルーティングを確認してみると、すでにUserの認証に必要なルーティングが設定されています。これはLaravel Fortify内部の/{your-workspace-root}/multi-auth/vendor/laravel/fortify/routes/routes.phpで定義されています。Userの認証関係のルーティングはこちらに全て任せることにしましょう。- login/logout処理に関してはAuthenticatedSessionControllerが担っていますので、Adminの認証もこちらを参考にしていきます。
+--------+----------+----------------------------------+---------------------------------+---------------------------------------------------------------------------------+---------------------------------------------------------+ | Domain | Method | URI | Name | Action | Middleware | +--------+----------+----------------------------------+---------------------------------+---------------------------------------------------------------------------------+---------------------------------------------------------+ | | POST | login | | Laravel\Fortify\Http\Controllers\AuthenticatedSessionController@store | App\Http\Middleware\EncryptCookies | | | | | | | Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse | | | | | | | Illuminate\Session\Middleware\StartSession | | | | | | | Laravel\Jetstream\Http\Middleware\AuthenticateSession | | | | | | | Illuminate\View\Middleware\ShareErrorsFromSession | | | | | | | App\Http\Middleware\VerifyCsrfToken | | | | | | | Illuminate\Routing\Middleware\SubstituteBindings | | | | | | | App\Http\Middleware\RedirectIfAuthenticated | | | GET|HEAD | login | login | Laravel\Fortify\Http\Controllers\AuthenticatedSessionController@create | App\Http\Middleware\EncryptCookies |multi-auth/vendor/laravel/fortify/routes/routes.phpRoute::group(['middleware' => config('fortify.middleware', ['web'])], function () { // Authentication... Route::get('/login', [AuthenticatedSessionController::class, 'create']) ->middleware(['guest']) ->name('login'); $limiter = config('fortify.limiters.login'); Route::post('/login', [AuthenticatedSessionController::class, 'store']) ->middleware(array_filter([ 'guest', $limiter ? 'throttle:'.$limiter : null, ])); Route::post('/logout', [AuthenticatedSessionController::class, 'destroy']) ->name('logout'); ~~省略~~multi-auth/vendor/laravel/fortify/src/Http/Controllers/AuthenticatedSessionController.php<?php namespace Laravel\Fortify\Http\Controllers; use Illuminate\Contracts\Auth\StatefulGuard; use Illuminate\Http\Request; use Illuminate\Routing\Controller; use Illuminate\Routing\Pipeline; use Laravel\Fortify\Actions\AttemptToAuthenticate; use Laravel\Fortify\Actions\EnsureLoginIsNotThrottled; use Laravel\Fortify\Actions\PrepareAuthenticatedSession; use Laravel\Fortify\Actions\RedirectIfTwoFactorAuthenticatable; use Laravel\Fortify\Contracts\LoginResponse; use Laravel\Fortify\Contracts\LoginViewResponse; use Laravel\Fortify\Contracts\LogoutResponse; use Laravel\Fortify\Fortify; use Laravel\Fortify\Http\Requests\LoginRequest; class AuthenticatedSessionController extends Controller { /** * The guard implementation. * * @var \Illuminate\Contracts\Auth\StatefulGuard */ protected $guard; /** * Create a new controller instance. * * @param \Illuminate\Contracts\Auth\StatefulGuard * @return void */ public function __construct(StatefulGuard $guard) { $this->guard = $guard; } /** * Show the login view. * * @param \Illuminate\Http\Request $request * @return \Laravel\Fortify\Contracts\LoginViewResponse */ public function create(Request $request): LoginViewResponse { return app(LoginViewResponse::class); } /** * Attempt to authenticate a new session. * * @param \Laravel\Fortify\Http\Requests\LoginRequest $request * @return mixed */ public function store(LoginRequest $request) { return $this->loginPipeline($request)->then(function ($request) { return app(LoginResponse::class); }); } /** * Get the authentication pipeline instance. * * @param \Laravel\Fortify\Http\Requests\LoginRequest $request * @return \Illuminate\Pipeline\Pipeline */ protected function loginPipeline(LoginRequest $request) { if (Fortify::$authenticateThroughCallback) { return (new Pipeline(app()))->send($request)->through(array_filter( call_user_func(Fortify::$authenticateThroughCallback, $request) )); } if (is_array(config('fortify.pipelines.login'))) { return (new Pipeline(app()))->send($request)->through(array_filter( config('fortify.pipelines.login') )); } return (new Pipeline(app()))->send($request)->through(array_filter([ config('fortify.limiters.login') ? null : EnsureLoginIsNotThrottled::class, RedirectIfTwoFactorAuthenticatable::class, AttemptToAuthenticate::class, PrepareAuthenticatedSession::class, ])); } /** * Destroy an authenticated session. * * @param \Illuminate\Http\Request $request * @return \Laravel\Fortify\Contracts\LogoutResponse */ public function destroy(Request $request): LogoutResponse { $this->guard->logout(); $request->session()->invalidate(); $request->session()->regenerateToken(); return app(LogoutResponse::class); } }基本的にFortifyの各クラスを参考に実装していきます。それでは作成していきましょう。
AdminLoginResponseクラスの作成
- multi-auth/app/Responsesディレクトリを作成し、その中にAdminLoginResponseを作成
- AdminLoginResponseは/{your-workspace-root}/multi-auth/vendor/laravel/fortify/src/Http/Responses/LoginResponse.phpからコピペして一部修正します。
muti-auth/app/Responses/AdminLoginResponse.php<?php namespace App\Responses; use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract; class AdminLoginResponse implements LoginResponseContract { /** * Create an HTTP response that represents the object. * * @param \Illuminate\Http\Request $request * @return \Symfony\Component\HttpFoundation\Response */ public function toResponse($request) { return $request->wantsJson() ? response()->json(['two_factor' => false]) : redirect()->intended('admin/dashboard'); // ログイン後に遷移させたいリダイレクト先を指定 } }login.blade.phpの修正
- Userと同じBladeを使用しますがAdmin用に分けても構いません。今回は簡易にするために、同じBladeですがguard変数があるかどうかでactionのURLを分けています。
multi-auth/resources/views/auth/login.blade.php<x-guest-layout> <x-jet-authentication-card> <x-slot name="logo"> <x-jet-authentication-card-logo /> </x-slot> <x-jet-validation-errors class="mb-4" /> @if (session('status')) <div class="mb-4 font-medium text-sm text-green-600"> {{ session('status') }} </div> @endif <!-- guard変数がセットされているかによってPOST先を変更 --> <form method="POST" action="{{ isset($guard) ? route('admin.login') : route('login') }}"> @csrf <div> <x-jet-label for="email" value="{{ __('Email') }}" /> <x-jet-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autofocus /> </div> <div class="mt-4"> <x-jet-label for="password" value="{{ __('Password') }}" /> <x-jet-input id="password" class="block mt-1 w-full" type="password" name="password" required autocomplete="current-password" /> </div> <div class="block mt-4"> <label for="remember_me" class="flex items-center"> <input id="remember_me" type="checkbox" class="form-checkbox" name="remember"> <span class="ml-2 text-sm text-gray-600">{{ __('Remember me') }}</span> </label> </div> <div class="flex items-center justify-end mt-4"> @if (Route::has('password.request')) <a class="underline text-sm text-gray-600 hover:text-gray-900" href="{{ route('password.request') }}"> {{ __('Forgot your password?') }} </a> @endif <x-jet-button class="ml-4"> {{ __('Login') }} </x-jet-button> </div> </form> </x-jet-authentication-card> </x-guest-layout>AttemptToAuthenticateクラスをapp/Actions/Admin配下に作成
認証処理を実施するクラスです。
- {your-workspace-root}/multi-auth/vendor/laravel/fortify/src/Actions/AttemptToAuthenticate.phpからコピペして一部修正します。
multi-auth/app/Actions/Admin/AttemptToAuthenticate.php<?php namespace App\Actions\Admin; use Illuminate\Contracts\Auth\StatefulGuard; use Illuminate\Validation\ValidationException; use Laravel\Fortify\Fortify; use Laravel\Fortify\LoginRateLimiter; class AttemptToAuthenticate { /** * The guard implementation. * * @var \Illuminate\Contracts\Auth\StatefulGuard */ protected $guard; /** * The login rate limiter instance. * * @var \Laravel\Fortify\LoginRateLimiter */ protected $limiter; /** * Create a new controller instance. * * @param \Illuminate\Contracts\Auth\StatefulGuard $guard * @param \Laravel\Fortify\LoginRateLimiter $limiter * @return void */ public function __construct(StatefulGuard $guard, LoginRateLimiter $limiter) { $this->guard = $guard; $this->limiter = $limiter; } /** * Handle the incoming request. * * @param \Illuminate\Http\Request $request * @param callable $next * @return mixed */ public function handle($request, $next) { if (Fortify::$authenticateUsingCallback) { return $this->handleUsingCustomCallback($request, $next); } if ($this->guard->attempt( $request->only(Fortify::username(), 'password'), $request->filled('remember') )) { return $next($request); } $this->throwFailedAuthenticationException($request); } /** * Attempt to authenticate using a custom callback. * * @param \Illuminate\Http\Request $request * @param callable $next * @return mixed */ protected function handleUsingCustomCallback($request, $next) { $user = call_user_func(Fortify::$authenticateUsingCallback, $request); if (!$user) { return $this->throwFailedAuthenticationException($request); } $this->guard->login($user, $request->filled('remember')); return $next($request); } /** * Throw a failed authentication validation exception. * * @param \Illuminate\Http\Request $request * @return void * * @throws \Illuminate\Validation\ValidationException */ protected function throwFailedAuthenticationException($request) { $this->limiter->increment($request); throw ValidationException::withMessages([ Fortify::username() => [trans('auth.failed')], ]); } }AdminLoginServiceProviderクラスの作成
- 先ほど作成したAttemptToAuthenticateクラスと後ほど作成するLoginControllerで使用するguardをDIするためにAdminLoginServiceProviderクラスを作成します。
$ php artisan make:provider AdminLoginServiceProvider Provider created successfully.multi-auth/app/Providers/AdminLoginServiceProvider.php<?php namespace App\Providers; use App\Http\Controllers\Auth\LoginController; use Illuminate\Contracts\Auth\StatefulGuard; use Illuminate\Support\Facades\Auth; use App\Actions\Admin\AttemptToAuthenticate; use Illuminate\Support\ServiceProvider; class AdminLoginServiceProvider extends ServiceProvider { /** * Register services. * * @return void */ public function register() { $this->app ->when([LoginController::class, AttemptToAuthenticate::class]) ->needs(StatefulGuard::class) ->give(function () { return Auth::guard('admin'); }); } /** * Bootstrap services. * * @return void */ public function boot() { // } }app.phpを修正
作成したAdminLoginServiceProviderが反映されるようにapp.phpを修正します。
multi-auth/config/app.php~~省略~~ /* * Application Service Providers... */ App\Providers\AppServiceProvider::class, App\Providers\AuthServiceProvider::class, // App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, App\Providers\FortifyServiceProvider::class, App\Providers\JetstreamServiceProvider::class, App\Providers\AdminLoginServiceProvider::class, // この行を追加Admin認証用のLoginControllerを作成
- ログイン画面表示や認証、ログアウトなどのアクションを受けるLoginControllerをapp/Http/Controllers/Auth配下に作成します。
- {your-workspace-root}/multi-auth/vendor/laravel/fortify/src/Http/Controllers/AuthenticatedSessionController.phpを参考に、先ほどまでで作った各クラスを利用するように修正します。
$ php artisan make:controller Auth/LoginController Controller created successfully.multi-auth/app/Http/Controllers/Auth/LoginController.php<?php namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use Illuminate\Contracts\Auth\StatefulGuard; use Illuminate\Http\Request; use Illuminate\Routing\Pipeline; use App\Actions\Admin\AttemptToAuthenticate; use Laravel\Fortify\Actions\PrepareAuthenticatedSession; use App\Responses\AdminLoginResponse; use Laravel\Fortify\Contracts\LogoutResponse; use Laravel\Fortify\Http\Requests\LoginRequest; class LoginController extends Controller { /** * The guard implementation. * * @var \Illuminate\Contracts\Auth\StatefulGuard */ protected $guard; /** * Create a new controller instance. * * @param \Illuminate\Contracts\Auth\StatefulGuard * @return void */ public function __construct(StatefulGuard $guard) { $this->guard = $guard; } /** * Show the login view. * * @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory */ public function create() { return view('auth.login', ['guard' => 'admin']); } /** * Attempt to authenticate a new session. * * @param \Laravel\Fortify\Http\Requests\LoginRequest $request * @return mixed */ public function store(LoginRequest $request) { return $this->loginPipeline($request)->then(function ($request) { return app(AdminLoginResponse::class); }); } /** * Get the authentication pipeline instance. * * @param \Laravel\Fortify\Http\Requests\LoginRequest $request * @return \Illuminate\Pipeline\Pipeline */ protected function loginPipeline(LoginRequest $request) { return (new Pipeline(app()))->send($request)->through(array_filter([ AttemptToAuthenticate::class, PrepareAuthenticatedSession::class, ])); } /** * Destroy an authenticated session. * * @param \Illuminate\Http\Request $request * @return \Laravel\Fortify\Contracts\LogoutResponse */ public function destroy(Request $request): LogoutResponse { $this->guard->logout(); $request->session()->invalidate(); $request->session()->regenerateToken(); return app(LogoutResponse::class); } }AdminDashboardControllerとadmin/dashboard.blade.phpの作成
- 管理者ログイン後のダッシュボード表示用にAdminDashboardControllerとadmin/dashboard.blade.phpを作成します。
- admin/dashboard.blade.phpはdashboard.blade.phpからコピペして一部編集します。
$ php artisan make:controller AdminDashboardController Controller created successfully.multi-auth/app/Http/Controllers/AdminDashboardController.php<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class AdminDashboardController extends Controller { public function index() { return view('admin.dashboard'); } }/multi-auth/resources/views/admin/dashboard.blade.php<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> <!-- わかりやすいようにここを修正--> 管理者ダッシュボード </h2> </x-slot> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg"> <x-jet-welcome /> </div> </div> </div> </x-app-layout>未ログイン時の挙動を追記
- User、Adminともに認証が必要なページ(今回はdashboard, admin/dashboard)にアクセスした場合、それぞれのログインページにリダイレクトさせるためにAuthenticate.phpに追記します。
multi-auth/app/Http/Middleware/Authenticate.php<?php namespace App\Http\Middleware; use Illuminate\Auth\Middleware\Authenticate as Middleware; class Authenticate extends Middleware { /** * Get the path the user should be redirected to when they are not authenticated. * * @param \Illuminate\Http\Request $request * @return string|null */ protected function redirectTo($request) { if (!$request->expectsJson()) { if ($request->is('admin/*')) { return route('admin.login'); } return route('login'); } } }web.phpの修正
- 管理者ログインとダッシュボード用のルーティングを追記します。
multi-auth/routes/web.php<?php use App\Http\Controllers\AdminDashboardController; use App\Http\Controllers\Auth\LoginController; use Illuminate\Support\Facades\Route; /* |-------------------------------------------------------------------------- | 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::prefix('admin')->group(function () { Route::get('login', [LoginController::class, 'create'])->name('admin.login'); Route::post('login', [LoginController::class, 'store']); Route::middleware('auth:admin')->group(function () { Route::get('dashboard', [AdminDashboardController::class, 'index']); }); }); Route::middleware(['auth:web', 'verified'])->get('/dashboard', function () { return view('dashboard'); })->name('dashboard');auth.phpの修正
- 最後にauth.phpを修正し、Admin用の認証ガードであるadminを定義します。
- ファイルを修正した後は
php artisan config:cache
で設定値を反映させましょう。multi-auth/config/auth.php~~省略~~ 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'admin' => [ 'driver' => 'session', 'provider' => 'admins', ], 'api' => [ 'driver' => 'token', 'provider' => 'users', 'hash' => false, ], ], ~~省略~~ 'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\Models\User::class, ], 'admins' => [ 'driver' => 'eloquent', 'model' => App\Models\Admin::class, ], // 'users' => [ // 'driver' => 'database', // 'table' => 'users', // ], ], ~~省略~~ 'passwords' => [ 'users' => [ 'provider' => 'users', 'table' => 'password_resets', 'expire' => 60, 'throttle' => 60, ], 'admins' => [ 'provider' => 'admins', 'table' => 'password_resets', 'expire' => 60, 'throttle' => 60, ], ],ビルトインサーバーを起動して(
php artisan serve
)管理者ログインができるか確認無事に管理者ログインできました
最後に
Fortifyではかなり多くのクラスが登場し、なおかつDIがかなり使われているため最初はなかなかコードを追うのが大変でしたが、少しずつ読み解いていくことで新しい認証スキャフォールドについて理解を深めることができました。Laravelはいいぞ。
誤字・脱字、間違った実装などありましたらコメント等いただけますと幸いです?
それでは良いLaravelライフを?
- 投稿日:2020-10-20T04:20:44+09:00
【準備編】Laravel 8(+jetstream, fortify)でマルチログイン
はじめに
Laravel8から標準のユーザー認証機能がlaravel/uiからLaravel Jetstream + Laravel Fortifyに変更されました。新しい認証機能もlaravel/uiと同様に導入自体は非常に簡単で、かつ多機能(プロフィール管理、チーム管理、二段階認証などなど)、必要に応じてconfigファイルで利用する機能を選択することが可能です。その一方で認証処理の実装が変わっていることもあり、拡張するには少し手間がかかります(ました)。ドキュメントでは下記のように言及されていますが、
Laravel Jetstreamが用意する認証スカフォールドを使う必要はないことに留意してください。このスカフォールドを使用しない選択をした場合、Laravelの認証クラスを直接使用し、ユーザーの認証管理を行う必要があります。
せっかくならば新しい認証機能使いたいぞ!
ということで、本記事では一般ユーザーと管理者ユーザーでログイン画面、ログイン後の画面を分けるマルチログインをLaravel JetstreamとLaravel Fortifyを使って実装していきます。
なお、Laravel Fortifyの詳しい処理を深く追ったり説明したり、ということはこの記事ではしません。動作環境
本記事では全てローカルで動作させます
- PHP 7.4.11
- MySQL 8.0.21
- Laravel Framework 8.10.0
マルチログインの仕様
- 一般ユーザー(User)、管理者(Admin)でログインユーザーとページを分ける
- ルーティングは以下とする(本記事ではドメインはlocalhost:8000として扱います)
- 一般ユーザーログインページ
- localhost:8000/login
- 一般ユーザートップページ
- localhost:8000/dashboard
- 管理者ログインページ
- localhost:8000/admin/login
- 管理者トップページ
- localhost:8000/admin/dashboard
- 両ユーザー共に未ログインの時にはトップページを表示することはできない(ログインページにリダイレクトさせる)
プロジェクトの準備
- プロジェクトの生成(今回はmulti-authというプロジェクトにしています)
- Blade + livewireを使用するので
Which Jetstream stack do you prefer?
は0を選択- teamsは使用しないので
Will your application use teams?
はnoを選択$ laravel new multi-auth | | | |,---.|--- ,---.|--- ,---.,---.,---.,-.-. ||---'| `---.| | |---',---|| | | `---'`---'`---'`---'`---'` `---'`---^` ' ' Which Jetstream stack do you prefer? [0] livewire [1] inertia > 0 Will your application use teams? (yes/no) [no]: > no Installing laravel/laravel (v8.1.0) - Installing laravel/laravel (v8.1.0): Downloading (100%) Created project in /{your-workspace-root}/multi-auth ~~省略~~ Application ready! Build something amazing.
- プロジェクトが生成されたらアセットのビルドを実行
$ cd multi-auth $ npm install && npm run dev
- .envのDB_DATABASEと同じ名前のDBを、ローカルのMySQLで作成してmigrationを実行
.envDB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=multi_auth ← これ DB_USERNAME=root DB_PASSWORD=$ php artisan migrate Migration table created successfully. Migrating: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_000000_create_users_table (42.86ms) Migrating: 2014_10_12_100000_create_password_resets_table Migrated: 2014_10_12_100000_create_password_resets_table (19.50ms) Migrating: 2014_10_12_200000_add_two_factor_columns_to_users_table Migrated: 2014_10_12_200000_add_two_factor_columns_to_users_table (22.99ms) Migrating: 2019_08_19_000000_create_failed_jobs_table Migrated: 2019_08_19_000000_create_failed_jobs_table (25.19ms) Migrating: 2019_12_14_000001_create_personal_access_tokens_table Migrated: 2019_12_14_000001_create_personal_access_tokens_table (39.09ms) Migrating: 2020_10_14_073012_create_sessions_table Migrated: 2020_10_14_073012_create_sessions_table (52.85ms)
- Adminモデルの作成とmigrationを実行
- create_users_table.phpからupメソッドの中身をコピペ
$ php artisan make:model Admin -m Model created successfully. Created Migration: 2020_10_14_075325_create_admins_tablecreate_admins_table.php~~省略~~ public function up() { Schema::create('admins', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->rememberToken(); $table->foreignId('current_team_id')->nullable(); $table->text('profile_photo_path')->nullable(); $table->timestamps(); }); }$ php artisan migrate Migrating: 2020_10_14_075325_create_admins_table Migrated: 2020_10_14_075325_create_admins_table (31.63ms)
- Adminモデルの内容を修正(User.phpからコピペ)
Admin.php<?php namespace App\Models; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Fortify\TwoFactorAuthenticatable; use Laravel\Jetstream\HasProfilePhoto; use Laravel\Sanctum\HasApiTokens; class Admin extends Authenticatable { use HasApiTokens; use HasFactory; use HasProfilePhoto; use Notifiable; use TwoFactorAuthenticatable; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', 'two_factor_recovery_codes', 'two_factor_secret', ]; /** * The attributes that should be cast to native types. * * @var array */ protected $casts = [ 'email_verified_at' => 'datetime', ]; /** * The accessors to append to the model's array form. * * @var array */ protected $appends = [ 'profile_photo_url', ]; }
- 一般ユーザーと管理者をシーダーで生成できるようにする
$ php artisan make:seeder UserSeeder Seeder created successfully. $ php artisan make:seeder AdminSeeder Seeder created successfully.UserSeeder.php<?php namespace Database\Seeders; use App\Models\User; use Illuminate\Database\Seeder; use Illuminate\Support\Facades\Hash; class UserSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { User::create([ 'name' => '一般ユーザー', 'email' => 'user@user.user', 'password' => Hash::make('password'), ]); } }AdminSeeder.php<?php namespace Database\Seeders; use App\Models\Admin; use Illuminate\Database\Seeder; use Illuminate\Support\Facades\Hash; class AdminSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { Admin::create([ 'name' => '管理者', 'email' => 'admin@admin.admin', 'password' => Hash::make('password') ]); } }DatabaseSeeder.php<?php namespace Database\Seeders; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { /** * Seed the application's database. * * @return void */ public function run() { $this->call(UserSeeder::class); $this->call(AdminSeeder::class); } }$ php artisan db:seed Seeding: Database\Seeders\UserSeeder Seeded: Database\Seeders\UserSeeder (250.64ms) Seeding: Database\Seeders\AdminSeeder Seeded: Database\Seeders\AdminSeeder (106.95ms) Database seeding completed successfully.
- ビルトインサーバーを起動してブラウザでlocalhost:8000を表示
- localhost:8000/loginにて一般ユーザーでログインできるか確認
$ php artisan serve Starting Laravel development server: http://127.0.0.1:8000 [Mon Oct 1 00:00:00 0000] PHP 7.4.11 Development Server (http://127.0.0.1:8000) started無事ログインできれば準備編は終了
実装編に続きます
- 投稿日:2020-10-20T03:55:35+09:00
Laravel7 でCSSが読み込めない
Laravel 7.25.0です。
CSSの場所:
Laravel/public/css/app.css
HTMLの場所
Laravel/resources/views/layouts.blade.php問題点
何故かCSSが読み込めなかった。
解決
<link rel="stylesheet" href="/css/app.css">を
<link rel="stylesheet" href="{{ asset('css/app.css') }}">に変えたら読み込めました。
原因
不明です。(すみません)
参考
https://teratail.com/questions/126308
おしまい。