- 投稿日:2019-05-02T22:24:18+09:00
ドメインモデル、ドメインロジックとは何かをコードを交えて考えてみる
この記事について
オブジェクト指向設計の文脈で度々目にする「ドメインモデル」や「ドメインロジック」というものが具体的に何を指しているのか、ドメイン以外のモデルってあるの?とか、じゃあアプリケーションロジックってなんだろう、というようなことをコードを交えて定義してみようという試みです。
はじめに
コード例は、マルチパラダイムなプログラミング言語 PHP で書きます。
対象読者は「ドメインが何を指すのかよく分からない」と思っている方ですが、オブジェクト指向の猛者の方にも間違いがないか見てもらいたいので読んでほしいです。
そもそも「ドメイン」とは
語義
MACMILLAN DICTIONARY によると
a particular area of activity or life
domain (noun) American English definition and synonyms | Macmillan Dictionaryと定義されています(「活動や生活における特定の領域」)。
ひとまず「領域」と定義してよさそうです。
ソフトウェア開発以外でのドメイン
本題に入る前に、ソフトウェア開発以外でドメインという言葉がどんな意味や用法で使われているかを見てみます。
インターネットドメイン
ネットワーク上の複数のコンピューターを管理するためのグループや組織を表す言葉。
ドメインとは - コトバンク.jp とか qiita.com みたいなやつですね、ネットワーク上にあるコンピュータの識別情報として使われます。元々は www.qiita.com などとドメインの前にホスト名をつけてウェブサーバーを明示していましたが、最近ではホスト名を省略するケースが多いようです。
「グループや組織」ということで、こちらも「領域」と捉えることができそうです。
事業ドメイン
企業の持続的な成長を可能とする自社特有の事業活動領域のこと
ドメイン | 用語解説 | 野村総合研究所(NRI)会社に所属しているひとであれば、あなたの会社の事業ドメインはなんですか、という問いに答えらたほうがいいかもしれません。
ようするにどんな活動で収益を上げていますか、ということですかね。すでに上の定義に「事業活動領域」とあるので、こちらも「領域」を指しています。
数学におけるドメイン
数学における写像の定義域(ていぎいき、英: domain of definition)あるいは始域(しいき、英: domain; 域, 領域)とは、写像の値の定義される引数(「入力」)の取り得る値全体からなる集合である。
定義域 - Wikipedia集合という言葉が登場しました。
詳しく知りたい方は「定義域」などでぐぐっていただくとして、ここでは「写像」についてのみ補足します。
写像は関数の一種で、入力値の集合 X に対して、何らかの関数 f を用いて変換したとき、X のそれぞれの要素に対応した出力値の集合 Y の要素が定まるようなとき、関数 f は写像である、といいます。
以下は Elixir で表現した写像の例です(最もシンプルにわかりやすく書けそうだったので選びました)。
Enum.map([1, 2, 3], fn x -> x * 2 end) # [2, 4, 6]始域([1,2,3])に対して終域([2,4,6])が定まるような関数
fn x -> x * 2 end
(入力パラメータを2倍して出力する)は写像です。
(入力値の集合を「始域 (=domain) 」というのに対し、出力値の集合を「終域 (=codomain)」といいます)つまり、数学におけるドメインとは、なんらかの関数に与える入力値の集合である、といえそうです。
(ここらへん理解が曖昧なので間違っていたらどなたか補足をお願いします)
ソフトウェア開発におけるドメイン
さて、ようやく本題です。
ソフトウェア開発におけるドメインという言葉の初出は寡聞にして知らないんですが、けっこう昔から使われている用語な気がします(少なくとも私が最初に読んだオブジェクト指向について書かれた本「Multi-Paradigm Design for C++ - James O. Coplien」では使われてたと記憶しています。1998年出版)。
本記事ではひとまず有名な Eric Evans の「ドメイン駆動設計」から抜粋します。
A sphere of knowledge, influence, or activity.
Evans, Eric. Domain-Driven Design日本語では「知識、影響、または活動の領域」となります。
Evans は "sphere" という単語を使っているのでついでにこちらの意味も調べてみましょう。
a particular area of interest, activity, work etc that is one of many parts of life
sphere (noun) American English definition and synonyms | Macmillan Dictionary「生活の一部となっているような関心事、活動、仕事などの特定の領域」みたいなかんじでしょうか。
"sphere" の定義自体にも "activity" が含まれていますが、"domain" の定義にも含まれていて、なにやら "activity"(= 活動)という概念ががとても重要な気がしてきました。
ここまでのまとめ
- 「ドメイン」とは、なんらかの「領域」であり、その範囲は使われる文脈によって異なる。
- ソフトウェアにおけるドメインは、事業ドメインと数学におけるドメインになんか近そう
- activity (活動)っていう単語/概念がなんか重要そう
といったところでしょうか。
個人的な理解
ソフトウェア開発におけるドメインは、そのソフトウェアがなにをするためのものなのか、という定義のうち、ウェブとかデータベースとかメールとか、そういう外部のソフトウェアや決まりごと(HTTPとかSQLとかSMTPとか)の無関係な部分、なのかな、というのがざっくりした私の理解です。
コードを使って表現してみる
さて、「ドメイン」の定義がなんとなくわかったところで、それを表現してみることにします。
おそらくこれらの定義を何回読み返しても、概念的なことを完全に理解するのは難しいんじゃないかっていう印象で、実際に用例を用いて補ってやる必要があるんじゃないかな、と思います。
本来ならダイアグラムなどを用いて、概念モデルで表現するべきなんでしょうけど、図を書くのがめんどくさいのですっ飛ばしてコードで表現することにします。
お題
複数の異なる用例があったほうがいいと思うので、2つの毛色の異なるサービスをお題にあげようと思います。
- メディア(Qiita)
- 転職支援サービス(Qiita Jobs)
(注:Qiita Jobs は一部の機能しか使ったことないので、データや振る舞いの部分を想像で補っている部分があります。Qiita 本体に関しても言えることですが、実際のモデルと異なることをご了承ください)
ドメインモデル
オブジェクト指向におけるモデルとは何か、という説明には以下のページに簡潔に記述がありましたので引用します。
モデルとは、何らかの実体があって、それを縮小(あるいは拡大)して細かい部分は省いて本質的なところだけを抽出して形成した形のことである。
オブジェクト指向モデル「何らかの実体」というのがややミスリーディングなかんじもしますが、ある概念の中から、アプリケーションの中で扱うデータや処理を必要なところだけ抜き出して表現する、という解釈でよさそうです。
(他によい定義や解釈があれば補足をお願いします
)
後述しますが、たとえば、「記事」という概念(この場合は実体も存在しうるが、実体がなくても概念として成立すればモデルはつくれるでしょう)を扱うアプリケーションをつくろうとしたとき、その「記事」という概念にはどんな属性があれば成立するでしょうか。
また、それがウェブアプリケーションであろうが、CUI プログラムであろうが変更されない部分、インタフェースやデータ形式に依存しない部分はどこでしょうか。
そういった点に注意しながら、ドメインモデルを探索してみましょう。
個人的には、たとえドメインモデルであってもアプリケーションとして実装されることが前提であり、データベースやフレームワークと完全に切り離して考えることは意味がないとは思っていますが、モデリングの訓練や探求自体には意味はあると思っていて、概念を抽出して名前をつけたり、処理をどの単位で区切るかを考えて名前をつけたり、といったことは役に立つと思っています。
メディアにおけるドメインモデル
メディア(Qiita)におけるドメインモデルには、以下のようなものがありそうです。
- 記事
- 執筆者
- タグ
- いいね
- ストック
- コメント
- 編集リクエスト
記事を例にとってみます。
Qiita の記事の特徴は、
- 特定の執筆者にひもづいている(匿名では投稿できない)
- タイトル、本文、タグ、などの属性がある
- 編集、削除ができる
- 限定共有投稿、公開を切り替えられる(ただし、一度公開した記事を非公開にはできない)
- ページビュー、いいね、ストックの数がひもづいている
- 最終更新日時から1年以上経過すると「この記事は最終更新日から○年以上が経過しています。」と表示される
といったところでしょうか。
いくつかデータと振る舞いをピックアップして、モデルを表現してみます。
上記のリストから単語を拾って、プロパティおよびメソッドにしてみます。
class Article { private $author; private $title; private $body; private $tags; private $lastUpdatedAt; public function __construct( Author $author, string $title, string $body, array $tags ) {} public function edit(string $title, string $body, string $tags) {} public function delete() {} public function sharePrivately() {} public function publish() {} }記事は、執筆者とタイトル、本文、タグがないと公開できないので、日時系のデータを除き、すべてをコンストラクタに渡すようにしました。
編集できるのはタイトルと本文、およびタグなので、
edit
メソッドにはその3つを渡すようにしています。それ以外のメソッドはいずれも引数なしです。
private と public という単語があるので、どうやら公開か非公開か、みたいな状態もプロパティとして持っておいたほうがよさそうなかんじがします。
...といったようなかんじでドメインモデルを表現してみました。
ここまで、URLとかユーザーとか、ウェブに関する単語や、データベースに保存する、みたいな概念を入れずにクラス定義をしました。
簡単ではありますが、ドメイン(Qiita というメディア)において、記事というモデルが、どんなデータを持ち、どんな振る舞いを持つのか、という点だけにフォーカスして表現可能である、ということは分かっていただけたのではないかと思います。
のちほどドメインロジックのところで、以下の2点を Qiita のドメインにおける特徴的な振る舞いとして取り上げようと思います。
- 限定共有投稿、公開を切り替えられる(ただし、一度公開した記事を非公開(=限定共有)にはできない)
- 最終更新日時から1年以上経過すると「この記事は最終更新日から○年以上が経過しています。」と表示される
転職支援サービスにおけるドメインモデル
続いて転職支援サービス(Qiita Jobs)では、こんなかんじです。
- 開発チーム
- 求職者
- 募集職種
- チャット
こちらのドメインでは、開発チームと求職者の関係性(協調)についてモデル化してみようと思います。
まずはデータ部分です。
class DeveloperTeam { /** Member[] */ private $members; } class Member { private $name; private $teamId; } class JobSeeker { private $name; } class Candidate { /** JobSeeker */ private $seeker; private $teamId; } class Chat { /** Message[] */ private $messages; } class Message { private $content; }これらのモデルには以下の特徴があります。
- JobSeeker は DeveloperTeam との Chat を開始することができる
- JobSeeker は 特定の DeveloperTeam と Chat を始めると、Candidate となり、Team とのひもづけが行われる
- Chat は、JobSeeker 側からしか開始できない
- Candidate および DeveloperTeam の Member は任意のタイミングで Chat に投稿できる
では、これらの特徴をモデルに落とし込んでみます(実装は省略)。
class JobSeeker { private $name; public function startChatWith(DeveloperTeam $team): Chat {} } class Chat { /** Candidate */ private $candidate; /** DeveloperTeam */ private $team; /** Message[] */ private $messages; public function post(Message $message): void {} }上のコードで表現できてないことで、ひとつ大事なことを忘れていました。
メッセージを書き込むアクターをどう表現するか、です。
Candidate および Member は任意のタイミングで Chat に投稿できる
とありますので、Candidate と Member 両方が Message の作成者になれそうです。
interface を使って表現してみます。
interface ChatParticipant { public function id(): int; public function name(): string; } class JobSeeker implements ChatParticipant { } class Member implements ChatParticipant { } class Message { private $content; /** ChatParticipant.id */ private $creatorId; }
Chat::post()
のインタフェースもちょっと変更します。class Chat { public function post(string $message, ChatParticipant $from): void { // メッセージを追加する処理はこんなかんじになりそう // $this->addMessage(new Message($message, $from->id())); } }Message の生成をだれにやらせるかというのを悩みますが、ひとまず Chat 内で生成するようにします。
お気づきと思いますが、JobSeeker と Member という異なるクラスで
Message::createId
に入る値を ChatParticipant で一意にしなければならないのが問題で、ここらへんはデータベースが絡んでくることになりそうですが、ChatParticipant の実態がアプリケーションのユーザーになったとしても型自体は変わらないので、上のコードを変える必要はなさそうです。このへんは他にもいくつかやり方ありそうなので、こんな構造にしたほうがよさそう、みたいなご意見があれば、コメントいただけると助かります。
ここまでのまとめ
メディア(Qiita)、転職支援サービス(Qiita Jobs)という2つのドメインについて、それら固有のデータや振る舞いをそれぞれプロパティやメソッドとしてモデル化してみました。
対象のアプリケーションでやりたいことや扱いたい情報を言語化し、その中から名詞や動詞を抜き出してプロパティやメソッドにしていく作業を何度も行って、洗練されたモデルが手に入るんだと思いますが、もしやったことなければ自分が携わっているサービスでもこういった活動をやってみるといいのではないでしょうか。
ドメインロジック
ドメインロジックとはなにか、を考える前に、ドメインモデルの中で表現したデータ以外の部分を振り返ってみます。
たとえば、メディアのモデルにあった「限定共有投稿」という概念を
sharePrivately
というメソッド名で表現しました。これはあくまでも名前なので、実際の振る舞いが「限定共有」になっていなければ偽りの名前になってしまいます。
また、転職支援サービスのモデルにあった「Chat は、JobSeeker 側からしか開始できない」という制約を
JobSeeker::startChatWith()
というメソッドで表現しましたが、new Chat()
を呼んでしまえば、どのクラスからでも生成できてしまいます。さて、上記を踏まえた上で、ドメインロジックとは何か、を考えてみます。
プログラムにおける処理の内容、手順、方法のこと。
ロジック - WikipediaMVC なウェブアプリケーションにおける標準的な「処理の内容」と「手順」は、以下のような感じになると思います。
- Controller が Request から入力を受け取る
- Model が入力を元になんらかのデータを構築したり変更したりする
- Controller が Model の振る舞いを元に View を構築する
- View のデータ(HTML)を Response として返す
この手順はウェブアプリケーション固有のものなので、ドメインロジックではありませんが、その他にもデータベースやメールといった外部のソフトウェア(やプロトコル、データ形式など)に関連するもの以外はドメインロジックと呼んでしまってよさそうです。
(こちらも他によい定義があれば補足をお願いします
)
メディアにおけるドメインロジック
以下の2つについて、ロジックを考えてみます。
- 限定共有投稿、公開を切り替えられる(ただし、一度公開した記事を非公開(=限定共有)にはできない)
- 最終更新日時から1年以上経過すると「この記事は最終更新日から○年以上が経過しています。」と表示される
まずは「一度公開した記事を非公開(=限定共有)にはできない」の部分からです。
呼び出し側では、こんなかんじで使うことになると思います。
$article = $articles->find($articleId); $article->sharePrivately();このとき、すでに公開されている記事に対して呼び出された場合には例外を送出するとします。
public function sharePrivately() { if ($this->isPublished()) { throw new InvalidStateException('すでに公開されている記事に対しては限定共有に戻すことはできません。'); } }(
InvalidStateException
クラスはドメインで定義した例外クラスです)
- 処理の内容は「限定共有状態にする」
- 処理の手順は「公開状態かチェックする → 公開状態であれば状態の変更ができない旨クライアントに知らせる」
- 処理の方法は「if 文で分岐し、例外を投げる」
となります。
続いて「最終更新日時から1年以上経過すると『この記事は最終更新日から○年以上が経過しています。』と表示される」の部分です。
これは Article クラスに処理をさせるかどうか悩むところですが、ここでは別クラスに切り出してみます。
記事の状態によって警告文を出す、という処理なので、
ArticleStateWarning
とでもしておきます。class ArticleStateWarning { private $article; public function __construct(Article $article) {} public function __toString() { $diffInYear = Carbon::today()->diffInYear($this->article->lastUpdatedAt(); if ($diffInYear < 1) { return ''; } return "この記事は最終更新日から{$diffInYear}年以上が経過しています。"; } }
- 処理の内容は「警告文を生成する」
- 処理の手順は「本日と記事の最終更新日との差分を年単位で計算する → 1未満であれば空文字、1以上であれば警告文を生成する」
- 処理の方法は「Carbon ライブラリを使って差分を計算し、if 文で文字列の生成を分岐させる」
となります。
これらの条件文に書かれたルールはドメイン固有のものであり、他のメディアでもし同様の処理があったとしても、必ずしもルールが一致するとは限りません。
また、クライアントのクラスにどのような形で結果を返すか、というのも、ロジックの一部といっていいでしょう。
転職支援サービスにおけるドメインロジック
同じような要領で転職支援サービスにおけるドメインロジックも考えてみます。
- Chat は、JobSeeker 側からしか開始できない
前述の
「Chat は、JobSeeker 側からしか開始できない」という制約を
JobSeeker::startChatWith()
というメソッドで表現しましたが、new Chat()
を呼んでしまえば、どのクラスからでも生成できてしまいます。という部分について、もうちょっと深掘りしてみましょう。
おそらく中身はこんなイメージになると思います。
// JobSeeker public function startChatWith(DeveloperTeam $team): Chat { $candidate = new Candidate($this, $team); $chat = new Chat($candidate, $team); // 他にも初期化処理があればここで return $chat; }処理の流れを単純化すれば「 Chat を生成しそれを返す」だけですが、いちおうこれまでと同様に3つの属性を書いておきます。
- 処理の内容は「Chat を生成する」
- 処理の手順は「JobSeeker から Candidate を生成する → Candidate と DeveloperTeam から Chat を生成する」
- 処理の方法は「それぞれのオブジェクトは new で生成する」
となります。
では、さらにこの処理を呼ぶであろうクライアント、Controller はどうなるでしょう。
// CreateChatController // POST /chats public function __invoke(Request $request) { // session か token か分からないがログインしているユーザーを取得する $user = Auth::user(); // User にひもづいている JobSeeker を探す $jobSeeker = JobSeeker::ofUser($user)->findOrFail(); // DeveloperTeam.id はリクエストから渡ってくる $team = Team::findOrFail($request->team_id); // Chat を生成する $chat = $jobSeeker->startWithChat($team); // レスポンスを返す(JSON形式を想定) return new Response($chat); }(ちなみに
loadUser()
やリクエストやらレスポンスやらが絡んでいる部分はアプリケーションロジックです)これを見ると、JobSeeker のインスタンスはユーザーから取得できるようになっているので、やろうと思えば開発チームのコンテキスト(開発チーム専用のアプリケーションまたは Controller クラスがあるはずです)からでも Chat の生成ができてしまうでしょう。
これを阻止するためにはどうすればいいでしょう?
ひとつ思いついたのは、完全に有効な手段ではないんですが、コンテキストを名前空間で分けて、求職者のコンテキストでは生成可能とし、開発チームのコンテキストでは生成不可とする、というやり方です。
namespace Domain\Models; class Chat { protected function __construct(Candidate $candidate, DeveloperTeam $team) {} } namespace JobSeeker\Models; class Chat extends BaseChat { public function __construct(Candidate $candidate, DeveloperTeam $team) {} } namespace Member\Models; class Chat extends BaseChat { }PHP ではパッケージ(名前空間)でアクセスを制限したりはできないので、 Member コンテキストから JobSeeker コンテキストの Chat クラスを使われたらこの目論見は突破されてしまいます。
まぁ、下手するとせっかく protected で宣言していても、意図がちゃんと伝わってないと public に変更されてしまったりするので、何事も完璧というわけにはいかないですね、そのへんはチームでいいかんじにバランスを取ればいいのではないかと思います。
ここまでのまとめ
ドメインロジックは「処理の内容、手順、方法」を説明できるものであるということと、使用する言語によって取れる手段が限られるので、その中からより良い選択ができるかどうか、というのがキモ、という気がします。
つくろうとしている/つくっているアプリケーション固有の概念のうち、セッションやデータベース、HTTPの世界の概念などと関係ない部分がドメインのロジックである、という説明はもうちょっと洗練できるんじゃないかという予感はありつつ、現時点では私の精一杯のものなので、ひとまずこれでご容赦いただければと思います。
おわりに
いかがでしたでしょうか?
「ドメイン」てのがよく分からん、という方
「ドメイン」「ドメインモデル」「ドメインロジック」という言葉/概念の指すものがなんとなく分かったでしょうか?
まだ分からん、ということであれば直接補足しますので、Twitter にてメンションください、よろしくお願いします(その結果腹落ちしてもらえたら、この記事に反映できればいいですね)。
他にも「ドメイン」とはこういうものだ、という意見をお持ちの方
ぜひコメント欄にてご意見お聞かせください。
参考書籍
- ドメイン駆動設計 エリック・エヴァンス
- オブジェクト指向設計実践ガイド Sandi Metz
- ユースケース駆動開発実践ガイド ダグ・ローゼンバーグ, マット・ステファン
- 現場で役立つシステム設計の原則 増田 亨
- 投稿日:2019-05-02T21:30:39+09:00
【PHP初心者向け】カプセル化とアクセス権について解説!
はじめに
- カプセル化とアクセス権について本やネットの情報から調べて理解したことをまとめました。
- もし、書いていることに何か間違いがある場合はご指摘いただけると嬉しいです。
カプセル化とは
- プロパティの値を変更されてしまわないように、他のクラスからのアクセスを制限すること。
- プロパティをカプセルで覆うことで、触れられないようにするようなイメージ。
アクセス権とは
- プロパティとメソッドへアクセスできる範囲を制限すること
- public、protected、privateをアクセス修飾子という
アクセス修飾子の種類
アクセス修飾子 アクセスできる範囲 public どこからでもアクセス可能 protected そのクラス自身、継承したクラス、親クラスからのみアクセス可能 private そのクラスからのみアクセス可能 アクセス権のあるプロパティを操作するメソッド
getter(ゲッター)メソッド
とsetter(セッター)メソッド
を使うことで、アクセス権があるプロパティを操作することができる。getter(ゲッター)
- アクセス権があるプロパティの値にクラスの外から取得するメソッド
- 「getプロパティ名」のように書く。
コード例
<?php class Products { private $name = "パソコン"; public function getName(){ return $this->name; } } $product = new Products(); echo $product->getName(); ?>実行結果パソコン
setter(セッター)
- アクセス権があるプロパティの値にクラスの外からセット(設定)するメソッド
- 「setプロパティ名」のように書く。
コード例
<?php class Products{ private $name; public function getName(){ return $this->name; } public function setName($name){ $this->name = $name; } } $product = new Products(); $product->setName("パソコン"); echo $product->getName(); ?>実行結果パソコン
- 投稿日:2019-05-02T21:18:31+09:00
【初心者】PHPをブラウザで表示する方法
未来電子テクノロジー株式会社でインターンをしている大学一回生です。
PHPをブラウザで表示するのが難しかったのでアウトプットします。問題点
ドッドインストールの、ローカル開発環境の構築(mac編)を参考にし、Virtualbox(6.0.6)とvagrant(2.2.4)とCyberduckをインストールし、PHPをブラウザで表示しようとしたが、Cyberduckの新規接続ができない。また、できたとしてもブラウザに表示できない。
解決策
1.Vagrantfileを編集する。
# -*- mode: ruby -*- # vi: set ft=ruby : # All Vagrant configuration is done below. The "2" in Vagrant.configure # configures the configuration version (we support older styles for # backwards compatibility). Please don't change it unless you know what # you're doing. Vagrant.configure("2") do |config| # The most common configuration options are documented and commented below. # For a complete reference, please see the online documentation at # https://docs.vagrantup.com. # Every Vagrant development environment requires a box. You can search for # boxes at https://vagrantcloud.com/search. config.vm.box = "bento/centos-6.8" # Disable automatic box update checking. If you disable this, then # boxes will only be checked for updates when the user runs # `vagrant box outdated`. This is not recommended. # config.vm.box_check_update = false # Create a forwarded port mapping which allows access to a specific port # within the machine from a port on the host machine. In the example below, # accessing "localhost:8080" will access port 80 on the guest machine. # NOTE: This will enable public access to the opened port # config.vm.network "forwarded_port", guest: 80, host: 8080 # Create a forwarded port mapping which allows access to a specific port # within the machine from a port on the host machine and only allow access # via 127.0.0.1 to disable public access # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1" # Create a private network, which allows host-only access to the machine # using a specific IP. config.vm.network "private_network", ip: "192.168.33.11" # Create a public network, which generally matched to bridged network. # Bridged networks make the machine appear as another physical device on # your network. # config.vm.network "public_network" # Share an additional folder to the guest VM. The first argument is # the path on the host to the actual folder. The second argument is # the path on the guest to mount the folder. And the optional third # argument is a set of non-required options. # config.vm.synced_folder "../data", "/vagrant_data" # Provider-specific configuration so you can fine-tune various # backing providers for Vagrant. These expose provider-specific options. # Example for VirtualBox: # # config.vm.provider "virtualbox" do |vb| # # Display the VirtualBox GUI when booting the machine # vb.gui = true # # # Customize the amount of memory on the VM: # vb.memory = "1024" # end # # View the documentation for the provider you are using for more # information on available options. # Enable provisioning with a shell script. Additional provisioners such as # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the # documentation for more information about their specific syntax and use. # config.vm.provision "shell", inline: <<-SHELL # apt-get update # apt-get install -y apache2 # SHELL end上のコードの、「#」がはずれている、
config.vm.network "private_network", ip: "192.168.33.11"の部分を編集しました。
ちなみにドットインストールでは、config.vm.network "private_network", ip: "192.168.33.10"でした。
参照(https://sazaijiten.work/docker_virtualbox_vagrant/)2.Cyberduckで新規接続
サーバは、先ほど変更した数字、ポートは22(ポートがなぜ22なのかは分からないです)、ユーザ名とパスワードはvagrantです。
3.ターミナルで操作
ターミナルでCyberduckで作ったファイルに入り、「ip a」を入力。
最後から3行目に「192.168.33.11」と表示されている。
次に、「php -S 192.168.33.11:8000」を入力し、(なぜ:8000なのかは分からないです)、ブラウザで「http://192.168.33.11:8000」と検索。
これでphpをブラウザで表示できます。まとめ
最後にブックマークをすると、phpで作った複数のファイルがブラウザで表示できるようになりました。
プログラミングを始めて一ヶ月も経っておらず、不備があるかもしれないので、どうぞご指摘ください。
- 投稿日:2019-05-02T19:14:00+09:00
[忘備録]laravel5.5 データベースjoin 結合
laravelを用いて初めてデータベースの結合をしてみると、想像以上に簡単だったので、忘備録として残しておこうと思う。
前提
データベースにlaravelデフォルトの
users
に加え、id
,uesr_id
,text
カラムを含むposts
テーブルを用意する。postsテーブルを作成する際はマイグレーションファイルで
user_id
カラムを次のように設定する。$table->integer('user_id')->unsigned(); $table->foreign('user_id')->references('id')->on('users');流れ
①モデル設定
②コントローラーから使う①モデル設定
今回はユーザーが複数のpostを持つというで、以下のような関係になります。
user has many posts posts belongs to userこの点を踏まえて、
User.php
,Post.php
に以下のコードを追記。User.phppublic function posts() { return $this->hasMany('App\Post'); }Post.phppublic function user() { return $this->belongsTo('App\User'); }これで準備は整いました。
②コントローラーから使う
今回はコントローラーで確認用
unction join()
という関数を作ります。Post.phpfunction join() { #postsテーブルのuser_idが1のレコードを全て取得 $user = User::find(1); $posts = $user->posts; #postテーブルのデータを全て取得 $posts = Post::all(); #上記で取得した「postsテーブルのuser_idが1のレコード」をひとつずつ表示 foreach ($posts as $post) { echo $post . "<br/>"; } #上記で取得した「postテーブルのデータ」から結合されたユーザーの名前をひとつずつ表示する。 foreach ($posts as $post) { echo $post->user->name . "<br/>"; } }これで、適当にルート設定をし、うまく表示されていることを確認することができました。
おかしな部分がある場合、指摘していただけるとありがたいです。
- 投稿日:2019-05-02T18:46:10+09:00
nginx php ubuntu備忘録
php7.2インストール
ninja@localhost:~$ sudo apt-get update ninja@localhost:~$ sudo apt-get upgrade ninja@localhost:~$ sudo apt-get install software-properties-common ninja@localhost:~$ sudo add-apt-repository ppa:ondrej/php ninja@localhost:~$ sudo apt-get update ninja@localhost:~$ sudo apt-get install php7.2 ninja@localhost:~$ sudo apt-get install php7.2-mbstring php7.2-mysql php7.2-xml php7.2-gd php7.2-zip php7.2-fpmapache削除
ninja@localhost:~$ sudo apt-get purge apache2 apache2-utils apache2-bin apache2-data ninja@localhost:~$ sudo apt-get autoremove /* apache2のフォルダの場所を探す。 */ ninja@localhost:~$ whereis apache2 ninja@localhost:~$ sudo rm -rf /etc/apache2nginxインストール
ninja@localhost:~$ sudo apt-get install nginxphp7.2(開発中にエラーがでたから入れたやつ)
ninja@localhost:~$ sudo apt-get install php-gmpphp7.2設定
ninja@localhost:~$ sudo vi /etc/php/7.2/cli/php.ini post_max_size = 10M cgi.fix_pathinfo=0 upload_max_filesize = 10M ninja@localhost:~$ sudo vi /etc/php/7.2/fpm/php.ini post_max_size = 10M cgi.fix_pathinfo=0 upload_max_filesize = 10Mnginx設定
/etc/nginx/sites-available/default.ninja@localhost:~$ sudo vi /etc/nginx/sites-available/default /* 全て消して書き換える*/ server { listen 80 default_server; listen [::]:80 default_server ipv6only=on; root /var/www/html; index index.php index.html index.htm; #server_name XXX.net; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:/var/run/php/php7.2-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }/etc/nginx/nginx.confninja@localhost:~$ sudo vi /etc/nginx/nginx.conf # httpの中に追加以下を追加 client_max_body_size 10m; # defaule 1mnginxの動作確認
ninja@localhost:~$ sudo nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful ninja@localhost:~$ sudo service php7.2-fpm restart ninja@localhost:~$ sudo service nginx restart ninja@localhost:~$ sudo ufw app list Available applications: Nginx Full Nginx HTTP Nginx HTTPS ninja@localhost:~$ sudo ufw allow 'Nginx Full'ブラウザから動作確認
XXX.XXX.XXX.XXX/info.phpにアクセスして表示されればOK
- 投稿日:2019-05-02T18:38:42+09:00
EC-CUBE 3でコードを編集してページを作成する
はじめに
EC-CUBEで固定ページを作成するときに、管理画面経由で作成すると、
user_data
というURLが付いちゃうのと、データベースの値に左右されてしまう点が気になったので、そもそもコントローラとか追加したらページ作成できるのではないかと思って試してみました。
(あと地味にURLの最後をスラッシュで終われない点も気になりました)こちらでのメリットとしては、プログラム側に記述されているので、データベースの値を併せなくてもページ自体は表示できるので、Gitなどでファイルを共有すればページも閲覧できるようになる点かと思います。
注意点
下記の流れはEC-CUBE3のコアファイルを編集しています。「とりあえずページ追加したい」という方以外は下記の流れで作らない方が良いと思います。
ルーティング
ルーティングは、
src/Eccube/ControllerProvider/FrontControllerProvicer.php
で操作しているようです。
中身はSilexのルーティングの記述ですね。例えば、
/foo/
でアクセスできるページをsrc/Eccube/Controller/StaticPageController
のfoo
メソッドから呼び出してfoo
という名前でurl
関数から呼び出せる様にする場合下記のような記述になります。
$c->match('/foo/', '\Eccube\Controller\StaticPageController::foo')->bind('foo');特にこの記述がコアファイルに直接記入してしまっているのが問題です。他にいい方法がアレば…。
プラグインでも良いんですけど、結局インストールと有効化することでDBに依存してしまうのが気になる。
コントローラ
次に
StaticPageController
を作ってみます。今回は単純に静的なページを追加するイメージで考えていますので、単純にビューを呼び出すだけの記述になりますが、特定カテゴリーの商品の一覧とかを表示させたい場合は、ここから必要なRepositoryを呼べばよいはずです。
<?php namespace Eccube\Controller; use Eccube\Application; class StaticPageController extends AbstractController { public function foo(Application $app) { return $app->render('Static/foo.twig'); } }これで、
Static/foo.twig
が読み込まれるようになりました。ちなみに、上記はコアファイル配下に設置していますが、
composer.json
にautoloadされるディレクトリを追加して、別の場所にコントローラを追加してしまえば、コアファイルとの切り離しも可能だと思います。ビュー
ビューファイルはコアファイルに含める必要はありません。
app/template/default/Static/foo.twig
を作成すれば、src
配下のファイルよりも優先されて読み込まれます。なお、管理画面上から追加していないので、titleやmeta情報などは少し強引に設定する必要があります。
{# titleに値を入れれば<title>追加される #} {% set title = 'ページタイトル' %} {# レイアウトテンプレートを指定 #} {% extends 'default_frame.twig' %} {# メタ情報を挿入する #} {% block meta_tags %} <meta name="description" content="ページの説明ほげほげ"> {# OGP情報とか、canonicalとか #} {% endblock %} {# CSSを読み込む #} {% block stylesheet %} <link rel="stylesheet" href="{{ app.config.front_urlpath }}/path/to/foo.css" type="text/css" media="all"> {% endblock %} {# JavaScriptを読み込む #} {% block javascript %} <script src="{{ app.config.front_urlpath }}/path/to/foo.js"></script> {% endblock %} {% block main %} {# メインコンテンツエリア、ここに色々記述する #} {% endblock %}これで
/foo/
にアクセスるとページが表示されるようになっているはずです。終わりに
とりあえずは思ったとおりにページが作れて満足です。(他にもっといい方法がありそうですけども)
EC-CUBE4ではこういった要望も満たされてると良いんですが調べていません、どうなんでしょうね?
- 投稿日:2019-05-02T17:05:46+09:00
5/2 PHP CookieとSessionの使い方
クッキーの作成と呼び出し
・setcookieでcoolieを作成
・var_dump($_COOKIE)で呼び出し
・time()+3600で有効期限の指定例
<?php setcookie("name", "Tanaka"time()+3600); var_dump($_COOKIE); ?>Session
サーバーに保存される一時データ
sessionの開始>session_start();
例
<?php session_start(); $_SESSION["name"] = "Suzuki"; var_dump($_SESSION); ?>その他必要な関数
unset関数
引数に渡した変数を開放する。配列+キーを指定することでそのキーのみ開放されます。
session_unset関数
$SESSION内の全ての変数を開放します。
session_destroy関数
$SESSION自体を破棄してしまいます。
- 投稿日:2019-05-02T16:43:44+09:00
5/2 PHP webサイトでのデータ送信メソッド
画面遷移のやり方
前提知識
GET ※よく使う URLで指定した内容をサーバーからもらう アドレスバーに内容が出る
POST ※よく使う URLで指定した内容をサーバーからもらう アドレスバーに内容が出ない
PUT
DELETE
HEAD
OPTIONS
TRACE
CONNECTGETメソッドの例
ファイル1
<!DOCTYPE html> <html lang="utf-8"> <head> <meta charset="utf-8"> <title>Send username</title> </head> <body> <form action="test.php" method="GET"> <input type="text" name="last_name" placeholder="苗字を入力してください"> <input type="text" name="first_name" placeholder="名前を入力してください"> <input type="submit" value="送信"> </form> </body> </html>ファイル2
<?php $last_name = ""; $first_name = ""; if ($_SERVER['REQUEST_METHOD'] === "GET") { $last_name = $_GET["last_name"]; $first_name = $_GET["first_name"]; } ?> <!DOCTYPE html> <html lang="utf-8"> <head> <meta charset="utf-8"> <title>Send username</title> </head> <body> <?php echo "苗字 : ".htmlspecialchars($last_name).PHP_EOL; ?> <?php echo "名前 : ".htmlspecialchars($first_name).PHP_EOL; ?> </body> </html>
- 投稿日:2019-05-02T15:40:49+09:00
5/2 PHPの例外処理
- 投稿日:2019-05-02T11:24:04+09:00
5/2 PHP オブジェクト操作における定義ファイルの読込
オブジェクト操作における定義ファイルの読込
…オブジェクト指向で一回一回定義をファイルに打ち込むのは面倒
そこで、定義ファイルを作成し、それを読み込ませれば良いのだわ例
<?php require "user.php"; // require_once "user.php"; $user_obj = new User("Tanaka"); $user_obj->say_hello(); ?>・ここでは別途用意したuser.phpファイルを読み込んでいる。
読み込の操作の一覧
require
require_once
include
include_once
autoloader(クラスにのみ使える)・require_onceは読込をスキップしてくれる。とりあえずはonceを使うべし
・requireとincludeは
- 投稿日:2019-05-02T11:24:04+09:00
5/2 PHP オブジェクト操作
オブジェクト操作における定義ファイルの読込
…オブジェクト指向で一回一回定義をファイルに打ち込むのは面倒
そこで、定義ファイルを作成し、それを読み込ませれば良いのだわ例
<?php require "user.php"; // require_once "user.php"; $user_obj = new User("Tanaka"); $user_obj->say_hello(); ?>・ここでは別途用意したuser.phpファイルを読み込んでいる。
読み込の操作の一覧
require
require_once
include
include_once
autoloader(クラスにのみ使える)・require_onceは読込をスキップしてくれる。とりあえずはonceを使うべし
・requireとincludeは名前空間の定義
作成したクラスやメソッドをひとまとめにする
例
<?php namespace Courage\User; コードの先頭に記述
- 投稿日:2019-05-02T02:50:50+09:00
Laravelのtrait Macroableを使って動的にメソッドを追加する
Macroableとは
Laravelをインストールしたときに一緒にインストールされるtraitです。
vendor\laravel\framework\src\Illuminate\Support\Traits\Macroable.phpにあります。
Macroableをuseするとそのクラスは定義されたクラス以外でも動的にメソッドを追加できるようになります。
ソースそのものは108行しかないのでシンプルです。
以下メソッド
public static function macro($name, $macro) public static function mixin($mixin) public static function hasMacro($name) public static function __callStatic($method, $parameters) public function __call($method, $parameters)仕組みとしてはmacro()でメソッド名と実行する内容を登録して、マジックメソッドの __call() か __callStatic() で使えるようにするといったものです。
hasMacro()は名前の通りmacro()で登録してあるかチェックするためのメソッドです。
mixin()は5.5から追加されました。名前で推測できるように引数にインスタンスを渡して、そのインスタンスのメソッドを使えるようにするためのものと判断出来ます。
ですが、癖が強いので使い勝手が良いかどうかは疑問です。後述します。使い方
class Human{ use \Illuminate\Support\Traits\Macroable; public function punch(){ echo "パンチ"; } } Human::macro('kick', function(){ echo "キック"; }); $human = new Human(); $human->punch(); $human->kick(); //結果 パンチキックLaravelで使うときはProviderで登録するのがまとまりがあって良いでしょう。
mixin()の使い方
まずはmixinメソッドがどのように定義されているか見てみましょう。
public static function mixin($mixin) { $methods = (new ReflectionClass($mixin))->getMethods( ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED ); foreach ($methods as $method) { $method->setAccessible(true); static::macro($method->name, $method->invoke($mixin)); } }リフレクションを使い、public,protectedのメソッドの一覧を取得してforeachでmacro()で登録する流れになっています。
ですが見てください。macroメソッドの第二引数の渡し方。$method->invoke($mixin)普通にメソッドの呼び出しをしています。
もしも直感的にメソッドを使うなら次のように間違って書いてしまうでしょう。class Cat{ public function say(){ echo "にゃー"; } } Human::macro('kick', function(){ echo "キック"; }); Human::mixin(new Cat()); $human = new Human(); $human->punch(); $human->kick(); //$human->say(); BadMethodCallExceptionが発生する //結果 にゃーパンチキックsay()を呼び出していないのに にゃー と鳴いてしまってます。
say()を呼び出すとBadMethodCallExceptionになります。どう解決しましょうか。
答えはシンプルです。say()メソッドの返り値をクロージャーにします。
class Cat{ public function say(){ return function(){ echo "にゃー"; }; } }このように定義すればsay()を呼び出してもBadMethodCallExceptionが発生しません。
結果はパンチキックにゃーの順で呼び出されます。mixin()は無名クラスを使って登録するのがベターな気がします。
Human::mixin(new class{ public function say(){ return function(){ echo "にゃー"; }; } });Macroableの使い所は?
第三者に拡張性のあるクラスを提供したい!といった場合くらいですかね。
自分で率先して使う場面というのはあんまり無いでしょう。
バグの原因になりそうです。実際の用途としてはLaravelで定義してあるクラスを拡張するのに使用するといったところでしょうか。
余談
日付操作ライブラリのCarbonにも同じようなMacroableのようなコードがあります。
個人的にマジックメソッドの __call() や __callStatic() はソースが追いにくくなるため苦手です。
- 投稿日:2019-05-02T02:33:49+09:00
Laravelチュートリアル - 汎用業務Webアプリを作る(1/4) Laradockで環境構築
はじめに
これまで私はDjangoでいくつかのWebアプリを作ってきましたが、とある事情から今後Laravelに触れる必要が出てきました。
そこで、既存のDjangoアプリをLaravelで再現するチュートリアル記事を作成し、その過程を通してLaravelについて学ぶことにしました。
題材には、CRUD機能、検索機能、認証機能を持ち、Djangoを勉強する際にも大変お世話になった、下記Webアプリとさせていただきました。
Djangoチュートリアル - 汎用業務Webアプリを最速で作る - Qiita
チュートリアル全体の構成
- Laradockで環境を構築する(本記事)
- テーブルとCRUD画面を作る(作成中)
- 検索機能を作る(作成予定)
- 認証機能を作る(作成予定)
環境
- macOS High Sierra 10.13.6
- php 7.2.16
- Laravel 5.5.45
- PostgreSQL 9.6.2
1. Laradockで環境構築する
Laradockのコピー
プロジェクトのルートとなるディレクトリに、Laradockをコピーします。
$ git clone https://github.com/Laradock/laradock.git
laradock
ディレクトリ配下のenv-example
ファイルをコピーし、.env
ファイルを作成します。$ cd laradock laradock $ cp env-example .envPostgreSQLのバージョンの指定
PostgreSQLは9.6.12を使用することにします。
PostgreSQLのDockerファイルを以下の通り編集します。laradock/postgres/DockerfileFROM postgres:9.6.12 LABEL maintainer="Ben M <git@bmagg.com>" CMD ["postgres"] EXPOSE 5432コンテナの起動
コンテナを起動します。
laradock $ docker-compose up -d --build workspace postgres php-fpm nginxLaravelのインストール
Laravelをインストールします。
まず、起動したコンテナの中に入ります。
laradock $ docker-compose exec workspace bash続いて、Laravelをインストールします。
バージョンは5.5.*
にします。
また、Laravelとしてのプロジェクト名は、src
にします。/var/www# composer create-project --prefer-dist laravel/laravel src "5.5.*"データベースを作成する
コンテナの中に入ったまま、今度はPostgreSQLに接続し、データベースを作成します。
まず、PostgreSQLに接続します。
/var/www# psql -U default -h postgresパスワードを入力します。
Password for user default:続いて、データベースを作成します。
データベース名は、sample
にします。default=# create database sample;PostgreSQLとの接続を終了します。
default=# \qLaravelのタイムゾーンを日本時間にする
Laravelのタイムゾーンを日本時間にします。
src/config/app.php<?php return [ //略 'timezone' => 'Asia/Tokyo', //略 ];.envファイルの編集
Laravelの.envファイルを編集し、LaravelからPostgreSQLに接続できるようにします。
src/.envDB_CONNECTION=pgsql DB_HOST=postgres DB_PORT=5432 DB_DATABASE=sample DB_USERNAME=default DB_PASSWORD=secret最後に
以上で環境構築は完了です。
次の記事では、テーブルとCRUD画面の作成を行います。
参考
- 投稿日:2019-05-02T00:32:04+09:00
Laravel 簡単なアプリケーション作成(1)
Laravel 簡単なアプリケーション作成(1)
Laravelを利用して簡単なアプリケーションを作成する手順
環境
前回作成したsampleappを流用する。
作成する画面、機能の概要
ID URL 機能 1 /home トップ画面表示 2 /auth/register ユーザ登録およびメール送信 3 /auth/login Login 4 /auth/logout Logout トップ画面の作成
http://homestead.test/home にアクセスしたらTop画面が表示されるようにする。
ディレクトリ構成
(base) mbp:sampleapp username$ tree -d -L 1 . ├── app ├── bootstrap ├── config ├── database ├── public ├── resources ├── routes ├── storage ├── tests └── vendorルーティング定義追加
routes/web.php// add Route::get('/home', function(){ return view('home'); });TOP画面のHTML作成(新規作成)
resources/home.blade.php<html> <head> <meta charset='utf-8'> </head> <body> Hello! World! </body> </html>http://homestead.test/home にアクセスしたらTop画面が表示される.
テストフレームワーク PHPUnit を試してみる
Laravelには、PHPを代表するテストフレームワークである「PHPUnit」が同梱される。
以下の2つのテストを行う。
- トップ画面のHTTPステータスコードが200
- トップ画面のレスポンスに"Hell! World!"の文字列が含まれていること
テストコードファイルの作成
(base) mbp:sampleapp username$ pwd /Users/username/code/sampleapp (base) mbp:sampleapp username$ php artisan make:test HomeTest Test created successfully. (base) mbp:sampleapp username$生成されたテストファイル
tests/Feature/HomeTest.php<?php namespace Tests\Feature; use Tests\TestCase; use Illuminate\Foundation\Testing\WithFaker; use Illuminate\Foundation\Testing\RefreshDatabase; class HomeTest extends TestCase { /** * A basic feature test example. * * @return void */ public function testExample() { $response = $this->get('/'); $response->assertStatus(200); } }以下に変更、追加
tests/Feature/HomeTest.php<?php namespace Tests\Feature; use Tests\TestCase; use Illuminate\Foundation\Testing\WithFaker; use Illuminate\Foundation\Testing\RefreshDatabase; class HomeTest extends TestCase { /** * A basic feature test example. * * @return void */ public function testExample() { $response = $this->get('/home'); $response->assertStatus(200); } public function testBody(){ $response = $this->get('/home'); $response-> assertSeeText("Hell! World!"); } }トップ画面のテストを実行
(base) mbp:sampleapp username$ pwd /Users/username/code/sampleapp (base) mbp:sampleapp username$ vendor/bin/phpunit tests/Feature/HomeTest.php PHPUnit 7.5.9 by Sebastian Bergmann and contributors. .. 2 / 2 (100%) Time: 100 ms, Memory: 14.00 MB OK (2 tests, 2 assertions) (base) mbp:sampleapp username$ユーザ登録の実装
ユーザ登録機能の実装を通じてリクエストの受信とバリデーション機能(検証、認可)に関して学ぶ。
手順概要
- データベース準備
- 認証/登録機能のコードを確認、ルーティングに追加
- 登録画面を作成
データベース準備
ユーザ情報を登録するテーブルを作成する。テーブル作成にはマイグレーション機能を利用する。
マイグレーション機能とはデータベースのスキーマ作成やデータ投入などをプログラムコードを使って処理する機能。
マイグレーションを行うためのファイルはdatabase/migrationsにある。databaseディレクトリの内容
(base) mbp:sampleapp username$ tree database/ database/ ├── factories │ └── UserFactory.php ├── migrations │ ├── 2014_10_12_000000_create_users_table.php │ └── 2014_10_12_100000_create_password_resets_table.php └── seeds └── DatabaseSeeder.php 3 directories, 4 filesマイグレーションファイル
2014_10_12_000000_create_users_table.php<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateUsersTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('users', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->rememberToken(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('users'); } }マイグレーション実行
vagrantで仮想環境にログイン
# cd ~/Homestead # vagrant ssh # cd ~/code/sampleapp/ vagrant@homestead:~/code/sampleapp$ php artisan migrate Migration table created successfully. Migrating: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_000000_create_users_table Migrating: 2014_10_12_100000_create_password_resets_table Migrated: 2014_10_12_100000_create_password_resets_table vagrant@homestead:~/code/sampleapp$MySQLに接続して作成したテーブルを確認する
vagrant@homestead:~/code/sampleapp$ mysql --host=localhost --user=homestead --password=secret homestead mysql: [Warning] Using a password on the command line interface can be insecure. Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 11 Server version: 5.7.25-0ubuntu0.18.04.2 (Ubuntu) Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> show tables from homestead ; +---------------------+ | Tables_in_homestead | +---------------------+ | migrations | | password_resets | | users | +---------------------+ 3 rows in set (0.00 sec) mysql>認証/登録機能をルーティングに追加
登録処理はコントローラーで処理する。コントローラーはMVCアーキテクチャを構成する要素の1つ。
サービス利用者からの入力、受信結果を返却するためのビューの選択、生成などを担う。登録処理はRegisterControllerクラスの showRegistorationForm メソッドと registor メソッドで行う。
ルーティング定義の追加
routes/web.phpRoute::get('auth/register', 'Auth\RegisterController@showRegistrationForm'); Route::post('auth/register', 'Auth\RegisterController@register');登録画面の作成
resources/views/auth にregister.blade.php として保存
resources/views/auth/register.blade.php<html> <head> <meta charset='utf-8'> </head> <body> <h1>ユーザ登録フォーム</h1> <form name="registform" action="/auth/register" method="post"> {{csrf_field()}} 名前:<input type="text" name="name" size="30"><span>{{ $errors->first('name') }} </span><br /> メールアドレス:<input type="text" name="email" size="30"><span>{{ $errors->first('email') }} </span><br /> パスワード:<input type="password" name="password" size="30"><span>{{ $errors->first('password') }} </span><br /> パスワード(確認):<input type="password" name="password_confirmation" size="30"><span>{{ $errors->first('password_confirmation') }} </span><br /> <button type='submit' name='action' value='send'>送信</button> </form> </body> </html>TOP画面を変更する
resources/views/home.blade.php<html> <head> <meta charset='utf-8'> </head> <body> Hello! @if (Auth::check()) {{\Auth::user()->name}}さん @else ゲストさん<br /> <a href="/auth/register">会員登録</a> @endif </body> </html>TOP画面にアクセスし、ユーザ登録フォームが表示されることを確認する。
試しに適当な値を入力し、送信ボタンを押し、 /home のページにリダイレクトされ、ログイン後の画面が表示される事を確認する。最後に、DBに登録されたデータを確認してみる。
vagrant@homestead:~$ mysql --host=localhost --user=homestead --password=secret homestead mysql: [Warning] Using a password on the command line interface can be insecure. Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 16 Server version: 5.7.25-0ubuntu0.18.04.2 (Ubuntu) Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> select * from users; +----+-----------------+----------------------+-------------------+--------------------------------------------------------------+----------------+---------------------+---------------------+ | id | name | email | email_verified_at | password | remember_token | created_at | updated_at | +----+-----------------+----------------------+-------------------+--------------------------------------------------------------+----------------+---------------------+---------------------+ | 1 | xxx | xxxx.com | NULL | xxxxx| NULL | 2019-05-01 08:44:24 | 2019-05-01 08:44:24 | +----+-----------------+----------------------+-------------------+--------------------------------------------------------------+----------------+---------------------+---------------------+ 1 row in set (0.00 sec) mysql>ユーザ認証
ログイン機能とログアウト機能を実装する
ルーティング追加
routes/web.php// Login Form Route::get('/auth/login', 'Auth\LoginController@showLoginForm'); Route::post('/auth/login', 'Auth\LoginController@login'); // Logout Route::get('/auth/login', 'Auth\LoginController@logout');ログインフォーム実装
resources/views/auth/login.blade.php<html> <head> <meta charset='utf-8'> </head> <body> <h1>ログインフォーム</h1> @isset($mesage) <p style="color:red">{{$message}}</p> @endisset <form name="loginform" action="/auth/login" method="post"> {{csrf_field()}} mailaddress: <input type="text" name="email" size="30" value="{{old('email')}}"><br /> password: <input type="password" name="password" size="30"><br /> <button type='submit' name='action' value='send'>Login</button> </form> </body> </html>TOP画面にログアウト機能を追加
views/home.blade.php<html> <head> <meta charset='utf-8'> </head> <body> Hello! @if (Auth::check()) {{\Auth::user()->name}}さん<br /> <a href="/auth/logout">Logout</a> @else ゲストさん<br /> <a href="/auth/login">Login</a><br /> <a href="/auth/register">会員登録</a> @endif </body> </html>ログアウト後の遷移先を変更する
/app/Http/Controllers/Auth/LoginController.php に以下のメソッドをオーバライドする
/app/Http/Controllers/Auth/LoginController.php// Override public function logout(\Illuminate\Http\Request $request) { $this->guard()->logout(); $request->session()->invalidate(); // return $this->loggedOut($request) ?: redirect('/'); return $this->loggedOut($request) ?: redirect('/home'); }元は sampleapp/vendor/laravel/framework/src/Illuminate/Foundation/Auth/AuthenticatesUsers.php
に定義されるメソッドだが、vendor/ 以下に直接変更を加えず、オーバライドしておこう