20210103のPHPに関する記事は15件です。

【ギリ初心者向け】Laravel Docker AWS(EC2) Webアプリ(PHP)を0から簡単にデプロイする方法(無料)①

できるようになること(MacOS前提です。同じことやればWinでも大丈夫)

Webアプリ(PHP)をDockerで作成し、AWSでデプロイする工程をわかりやすく書いていく!!
プログラミングはできるようになったけど作ったものを世の中に出したいけどわからん!となった人向けです

①〜③の3部構成になります
①全体像と全体の流れ 目次みたいなもん
②Dockerを利用した理由とプロジェクト作成手順
③作成したプロジェクトをAWS(EC2インスタンス)にUPして世の中にだす!

※知らない単語等も詳しく説明するつもりだが。都度ググってくださいな!ググる力もエンジニアの力の一つらしいですわ。(プロが言ってた)

まずは全体像から

qiita-square
全体のイメージはこんな感じ。
これを作成すれば作成したWebアプリを世の中に出せる!!

0から世の中に出すまで順序(目次みたいなもんですわ)

1. DockerをPCにインストール

2. Project(ディレクトリを作成)

3. Projectの中でDockerを使って環境構築
- Webサーバー(nginx)をインストール
- アプリケーションサーバー(php:7.4-fpm)をインストール, PHPパッケージ管理ツール(Composer)インストール
- データベースサーバー(mysql)をインストール
- PHPを扱うフレームワーク(Laravel)をインストール

4. ProjectをGithubにUP

5. ローカルの状態で自由自在にプログラミング!!
ここでLaravel(PHP)でプログラミングするよ!!

6. できたものをGithubにUP

7. AWSに登録してEC2インスタンス(Ubuntu)を作成!!

8. EC2インスタンス(Ubuntu)の中に環境構築
- PHPをインストール
- Webサーバー(nginx)をインストール
- アプリケーションサーバー(php:7.4-fpm)をインストール, PHPパッケージ管理ツール(Composer)インストール
- データベースサーバー(mysql)をインストール
- PHPを扱うフレームワーク(Laravel)をインストール

9. EC2インスタンス (Ubuntu)内のディレクトリにgit clone。Githubからプロジェクトを持ってくる!!

10. 該当URLにアクセスして表示!!デプロイ成功!

具体的にどうすればよいか??

疲れたので今回の記事ではこんな感じにします
次回はDockerを使った環境構築からGithubにアップするところまで
それでは②へGO

Qiita初投稿で読みづらい箇所や誤っている箇所などがあると思いますが、優しくコメントでご指摘いただけると幸いです。

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

Amazon Linux2でLNPP環境を構築する

Amazon Linux2上に、LAMP環境ならぬLNPP環境を構築する手順です。

LAMPとの比較

今回構築するLNPP環境を、お馴染みLAMP環境と比較すると以下の通り。

LNPP LAMP
OS Linux Linux
WEBサーバ Nginx Apache
DB PostgreSQL MySQL
プログラミング言語 PHP PHP

WEBサーバをApacheではなく、Nginx、DBをMySQLではなく、PostgreSQLにしています。
※呼び方間違ってたら教えてください。

環境、バージョン情報

  • OS: Amazon Linux2
  • WEBサーバ: Nginx1.18.0
  • DB: PostgeSQL 11.5
  • プログラミング言語: PHP 7.4.11
  • クライアントPC: Windows10(PuTTYにてAmazon Linux2に接続)

手順

1. ソフトウェアのインストール

まず、Amazon Linux2環境にSSH接続して、環境の構築に必要な各種ソフトウェアをインストールします。
今回は、Amazon Linux2のExtras Libraryのお力を借りることにしました。

(1) 以下のコマンドを実行し、Extras Libraryで利用可能なトピックの一覧を確認します。

amazon-linux-extras

初期状態では以下のようになっているはず。

・・(以上省略)
 35  kernel-ng                available    [ =stable ]
 36  BCC                      available    [ =0.x  =stable ]
 37  mono                     available    [ =5.x  =stable ]
 38  nginx1=latest            available      [ =stable ]
 39  ruby2.6                  available    [ =2.6  =stable ]
 40  mock                     available    [ =stable ]
 41  postgresql11=latest      available     [ =11  =stable ]
 42  php7.4=latest            available     [ =stable ]
 43  livepatch                available    [ =stable ]
 44  python3.8                available    [ =stable ]
 45  haproxy2                 available    [ =stable ]
(以下省略)・・

(2) Nginx, PostgreSQL, PHPをインストールします。
以下3つのコマンドを順に実行しましょう。

sudo amazon-linux-extras install nginx1
sudo amazon-linux-extras install postgresql11
sudo amazon-linux-extras install php7.4

※うちPostgreSQLは、psqlのコマンドがインストールされるだけのようなので、普通にインストールもします(以下のコマンドを実行すればOK!)

sudo yum install postgresql-server postgresql-devel postgresql-contrib

(3) 再度、amazon-linux-extrasコマンドを実行し、Nginx, PostgreSQL, PHPが有効になっていることを確認します。

amazon-linux-extras

以下のように、available → enabledに変わっていればOK!

・・(以上省略)
 35  kernel-ng                available    [ =stable ]
 36  BCC                      available    [ =0.x  =stable ]
 37  mono                     available    [ =5.x  =stable ]
 38  nginx1=latest            enabled      [ =stable ]
 39  ruby2.6                  available    [ =2.6  =stable ]
 40  mock                     available    [ =stable ]
 41  postgresql11=latest      enabled      [ =11  =stable ]
 42  php7.4=latest            enabled      [ =stable ]
 43  livepatch                available    [ =stable ]
 44  python3.8                available    [ =stable ]
 45  haproxy2                 available    [ =stable ]
(以下省略)・・

2. Nginx

(1) まず、以下のコマンドを実行し、Nginxの設定を確認しましょう。

vi /etc/nginx/nginx.conf

HTTP接続の場合のドキュメントルートは、
/usr/share/nginx/html
ですので、配下にWEBページで表示させたいファイルを作成します(もちろん設定変更してもOK)

ちなみに、該当箇所は以下(rootと記載のある部分)。

・・(以上省略)
    server {
        listen       80;
        listen       [::]:80;
        server_name  _;
        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;
(以下省略)・・

(2) 以下の通り実行して、PHPの設定内容を表示する、index.phpを作成しましょう。

sudo vi /usr/share/nginx/html/index.php

ファイルが開いたら、insertモードで以下の内容をコピペ→保存します。

<?php 
phpinfo();

(3) Nginxを起動します。

sudo systemctl start nginx.service

(4) ブラウザで以下のURLを叩いて、PHP設定内容が表示されることを確認します。

http://パブリック IPv4 DNS/index.php

image.png

※パブリック IPv4 DNSはAWSのコンソール(EC2)より、確認できます。

3. PostgreSQL

続いてPostgreSQLの設定を行います。

(1) まず、以下のコマンドを実行して初期化をしましょう。

sudo postgresql-setup initdb

(2) 設定ファイルの格納先を確認します。

sudo find / -name pg_hba.conf

自分の場合は、以下の通りでした。

/var/lib/pgsql/data/pg_hba.conf

(3) 設定ファイルを開き、以下の通り内容を変更して、一時的にパスワードがなくてもログインできるようにします(後続の手順で設定)。

sudo /var/lib/pgsql/data/pg_hba.conf

■変更前

・・(以上省略)
# IPv4 local connections:
host    all             all             127.0.0.1/32            ident
# IPv6 local connections:
host    all             all             ::1/128                 ident
(以下省略)・・

↓ (「ident」(2ヶ所)を「trust」に変更)

■変更後

・・(以上省略)
# IPv4 local connections:
host    all             all             127.0.0.1/32            trust
# IPv6 local connections:
host    all             all             ::1/128                 trust
(以下省略)・・

(4) PostgreSQLを起動し、ログイン → パスワード設定します。

sudo systemctl start postgresql.service
psql -h localhost -p 5432 -U postgres

「postgres=#」と表示されたらログインできているので、以下のDDLを実行してパスワード設定しましょう。

alter role postgres with password 'postgres';

※上記では、ユーザー: postgresのパスワードをpostgresに設定しています。

(5) 設定ファイルを以下の通り変更して、パスワードなしではログインできないようにします。

・・(以上省略)
# IPv4 local connections:
host    all             all             127.0.0.1/32            md5
# IPv6 local connections:
host    all             all             ::1/128                 md5
(以下省略)・・

※(3)の「■変更後」の「trust」(2ヶ所)を「md5」に変更しています。

(6) pdo_pgsqlが存在するか確認します(今回はPDO接続するため)

php -m | grep pdo

pdo_pgsqlが表示されなければ、以下コマンドを実行してインストールします。

sudo yum install --enablerepo=remi,remi-php74 php-pgsql

(7) fpm-phpを再起動して、変更を反映させます。

sudo systemctl restart php-fpm

※WEBサーバがApacheの場合はApacheの再起動なのですが、Nginxの場合はfpm-phpが再起動の対象となります。

(8) ブラウザから接続確認するためのpostgres.phpを作成しましょう。

sudo vi /usr/share/nginx/html/postgres.php

insertモードにて、以下の通り記述→保存。

<?php

$DBHOST = "127.0.0.1";
$DBPORT = "5432";
$DBNAME = "postgres";
$DBUSER = "postgres";
$DBPASS = "postgres";

try{
  //DB接続
  $dbh = new PDO("pgsql:host=$DBHOST;port=$DBPORT;dbname=$DBNAME;user=$DBUSER;password=$DBPASS");
  print("接続成功".'<br>');
}catch(PDOException $e){
  print("接続失敗".'<br>');
  print($e.'<br>');
  die();
}
//データベースへの接続を閉じる
$dbh = null;
?>

※ソースコードは、@ga_ku さんの PHPでPostgreSQLに接続する方法 | Qiitaより拝借しました(一部改変)。

(8) ブラウザで以下のURLを叩いて、「接続成功」と表示されればOK!

http://パブリック IPv4 DNS/postgres.php

image.png

これにて、環境構築 → ブラウザからの動作確認が一通りできました!

終わりに

Apache, MySQLもいいけれど、あまり馴染みのないNginx, PostgreSQLにも触れてみたくって。
特にPostgreSQLとPHPの連携のところ(PDO)で躓いたけど、できてよかった。

参考

LAMPとの比較

手順

1. ソフトウェアのインストール

2. Nginx

3. PostgreSQL

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

※学習用メモ デザインパターン:Proxy編[PHP]

Proxyパターンとはどういうものか

Proxyとは代理人という意味です。

Proxy(プロキシ、代理人)とは、大まかに言えば、別の物のインタフェースとして機能するクラスである。その「別の物」とは何でもよく、ネットワーク接続だったり、メモリ上の大きなオブジェクトだったり、複製がコスト高あるいは不可能な何らかのリソースなどである。(wikipediaより)

要約すると、処理全体に大きなリソースを要する場合、その一部の処理をProxy(代理人)がこなす、というものです。

登場するクラス

Subject:ProxyとRealSubjectを同一視するためのインターフェース
Proxy:RealSubjectの処理を一部代替するクラス。
 自身で処理できないものはRealSubjectに任せる。Proxyでは、本当にRealSubjectが必要となった際のみRealSubjectを生成する。
RealSubject:代理人であるProxyが処理できない際に処理を行うReal(本人)のクラス

Proxyパターンのメリット

・メリット
多くのリソースが必要となる処理が必要ない場合、代理人のクラスその部分以外の処理を任せることで処理を省力化できる

[参考]
https://ja.wikipedia.org/wiki/Proxy_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3
https://qiita.com/i-tanaka730/items/47efbab5eccc2d36759f
https://tic40.hatenablog.com/entry/2017/06/21/080000
https://www.ritolab.com/entry/142

サンプルコード

仕様

・キャラクターの情報を表示します。
・キャラクターの画像の読み込みに時間を要するため、画像の表示の必要が無い場合は処理をProxy(代理人)のクラスにて行います。

ProxyとRealSubjectのインターフェース(Subject)

CharacterInfoInterface.php
/**
 * Subjectインターフェース
 */
interface CharacterInfoInterface
{
    public function getCharacterInfo();
    public function displayImage();
}

キャラクターのクラス

Character.class.php
/**
 * キャラクターのクラス
 */
class Character
{
    private $name = "";
    private $img = "";
    private $profession = "";

    public function __construct($name, $img, $profession)
    {
        $this->name = $name;
        $this->img = $img;
        $this->profession = $profession;
    }

    public function getName()
    {
        return $this->name;
    }

    public function getImg()
    {
        return $this->img;
    }

    public function getProfession()
    {
        return $this->profession;
    }
}

代理人として処理を行うクラス(Proxy)
ここでは画像表示以外の処理を代理で行います。
画像表示についてはRealSubjectに任せています。

CharacterInfoProxy.class.php
/**
 * キャラクター情報の代理人クラス
 */
class CharacterInfoProxy implements CharacterInfoInterface
{
    private $character = "";

    public function __construct($character)
    {
        $this->character = $character;
    }

    public function getCharacterInfo()
    {
        return "名前:" . $this->character->getName() . "\n" .
        "職業:" . $this->character->getProfession() . "\n";
    }

    public function displayImage()
    {
        $real = new RealCharacterInfo($this->character);
        return $real->displayImage();
    }
}

本人クラス(RealSubject)
ここでは画像表示処理に必要なリソースが大きい仕様のため、sleepメソッドを用いて疑似的に重くしています。
また、動作の流れが分かるよう都度出力を行っています。

RealCharacterInfo.class.php
/**
 * キャラクター情報の本人クラス
 */
class RealCharacterInfo implements CharacterInfoInterface
{
    private $character = "";

    public function __construct($character)
    {
        $this->character = $character;
        echo "====RealCharacterInfoを生成しました。====\n";
    }

    public function getCharacterInfo()
    {
        return "名前:" . $this->character->getName() . "\n" .
        "職業:" . $this->character->getProfession() . "\n";
    }

    public function displayImage()
    {
        echo "画像表示処理中...\n";
        for ($i = 0; $i < 2; $i++) {
            sleep(1);
            echo "*\n";
        }
        echo "完了\n";

        return $this->character->getImg();
    }
}

使ってみる

$character = new Character("影虎", "/AAA/kagetora.png", "将軍");
$characterInfoProxy = new CharacterInfoProxy($character);

print_r($characterInfoProxy->getCharacterInfo());
print_r($characterInfoProxy->displayImage());

/*結果
名前:影虎
職業:将軍
====RealCharacterInfoを生成しました。====
画像表示処理中...
*
*
完了
/AAA/kagetora.png
*/

まとめ

・Proxyパターンは、処理に大きなリソースを要する場合、その一部の処理をProxy(代理人)がこなすことで処理を省力化できます。
・このパターンは今までのパターンとはやや視点が異なり、処理のリソースなどに目を向けるものでありましたが、今まであまり処理のリソース等について考慮できていなかった為今後のプログラム作成・設計の際にも意識しておこうと感じました。

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

aaaa

aaaaaaaaaaa

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

[Laravel] コントローラー→ビューの値渡し・view, redirect

概要

PHP歴3ヶ月、Laravel歴1ヶ月の初学者です。私が混乱した「コントローラーで処理した変数をビューへ渡す方法」や「viewやredirectの意味の違い」をまとめました。同じように頭を抱える初学者の方が一人でも減り、下記コードの赤枠部に関する悩みが少しでも解決できれば幸いです。
show.png

開発環境

PHP 7.2.34 / Laravel 6.20.5

コントローラーからビューへ値を渡す方法

①withメソッド
②view関数の第二引数に[ ]
③view関数の第二引数にcompact関数

①withメソッド

記述方法:view('ビューファイル名')->with(変数名, 引数);

渡す変数が1つの場合

Controller.php
 //渡す変数が1つの場合
 public function withPattern()
 {
  $message = "Hello";
  return view('test')->with('message',$message);
 }

 //渡す変数が複数の場合 (配列として記述。)
 public function withPattern2()
 {
  $message = "Hello";
  $name = "John";
  return view('test')->with([
                       'message'=> $message,
                       'name' => $name,
                       ]);
 }

②viewの第二引数に[ ]

記述方法:view('ビューファイル名', ['変数名' , 引数 ]);

Controller.php
 //渡す変数が1つの場合
 public function SecondArgumentPattern()
 {
  $message = "Hello";
  return view('test',['message',$message]);
 }

 //渡す変数が複数の場合 (配列として記述。)
 public function SecondArgumentPattern2()
 {
   $message = "Hello";
   $name = "John";
   return view('test',[
                       'message' => $message,
                       'name' => $name,
                      ]);
 }

③view関数の第二引数にcompact関数

記述方法:view('ビューファイル名', compact('変数名'));

Controller.php
 //渡す変数が1つの場合
 public function CompactPattern(){
  $message = "Hello";
 return view('test',compact('message'));

 //渡す変数が複数の場合 (変数名をカンマで区切って記述。)
 public function CompactPattern2(){
  $message = "Hello";
  $name = "John";
 return view('test',compact('message','name'));
 }

ビューは{{変数名}}で①②③全て展開可能(変数が複数でも同様)

view.php
 <p>
     日本語のこんにちは,英語では{{$message}}です // Hello
 </p>

比較すると、変数名のみで記述ができるcompact関数で値を渡すこと方が可読性が良さそうです。特に変数が増えてきたときに顕著ですね。(これは私の考察です。)

参考:https://laraweb.net/knowledge/1345/

view()とredirect()の違い

viewとredirectの挙動について実際に検証しました。様々なQiita記事を見ましたが、「これ本当?」と思われるものがあったので…

view()はresouces->viewファイルを読みにいき、
redirect()はweb.phpのルーティング情報を読みにいくことが大きな違いのようです。
下記の処理をそれぞれ呼び出していきます。

Controller.php
public function view()
 {   
    $message = "viewの表示";
    return view('/page',compact("message"));
 }
public function view2()
 {   
    return view('/page');
 }
public function redirect()
 {   
    return redirect('/view');
 }
web.php
Route::get('/view','Controller@view');
Route::get('/view2','Controller@view2');
Route::get('/redirect','Controller@redirect');
page.blade.php
<h1>
    これは{{$message}}です。
</h1>

①URLで/viewを呼び出す

view.png
勿論、コントローラーから変数messageがviewへ渡されpageが表示される。

②URLで/view2を呼び出す

次のようなエラーが発生。
view2.png

Undefined variable: message (変数messageに値がないよ)
view2メソッドは変数messageをviewへ渡していないため、エラーが出力される。

③URLで/redirectを呼び出す。

view.png
redirectはweb.phpに記載されているルーティング情報が読み込まれるため、上記の例ではweb.phpのRoute::get('/view','Controller@view');が読み込まれる。これにより変数messageをviewへ渡すことができるためpageが表示される。

最後に

私はLaravelを初めて触った時にコントローラーの戻り値の理解にかなり苦しみました。
書籍やネット情報で調べましたが、書き方にかなり差があってごちゃごちゃになりました。。
初歩的な部分ではありますが、初学者の方の理解の助けになれればと思います。
viewやredirectに関してはもう少し深掘りして更新していきます。
(もしこの記事に誤りがありましたらご教授いただけると幸いです。)

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

緯度経緯をURLへ変換するサービス、マップクリエイター

マップクリエイターとは、どんなWebサービスですか?
Googleマップの任意の地点をWebページ化するサービスです。場所をWebページ化することで空間にテキストが紐付くので、検索サイトから検索した時スムーズにその場所がどこにあるのかが、日本国民全員に分かるようになります。

マップクリエイターを作ろうと思ったきっかけは何だったのでしょうか?
旅行に行った時よくバスをよく利用するのですが、田舎のほうになるとバス停がどこにあるのか分からなかったり、バス停が分かってもどのバスがどこへ向かうのかが分からないことが多々あったので、それを上手い具合に解決したいと思って作りました。

そこで、ITとかにあまり詳しくない人でも、特定の場所に対して簡単にテキストを埋め込めるサービスがあれば、Googleで検索した時にその場所の詳細がすぐ分かるだろうと思って作りました。

バス停だけにとどまらず、地元の名所やつっこんだ観光案内(雑学など)を書けば、地域復興にも繋がるかなと思います。

機能面でこだわった点などありますか?
Google マップでは場所によっては緯度経緯が表示されず、プラスコードというアルファベットだけの表示になることもあるので、プラスコードにも対応させました。また悪用防止のため、作成したページを削除する機能も付けました。

(マップクリエイターは2018年に作ったwebサービスです)

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

【PHP7技術者認定初級試験】受けてみて

背景

なぜ受けたのか?

・実業務でPHPを書く機会がなかったから
・Webエンジニアとして知っておきたかったから
・資格取得奨励金が出るから

受験まで

勉強期間は2週間。平日の朝、夜で2h程度勉強しました。

勉強に使ったもの

・公式問題集(PHP7技術者認定[初級]試験問題集)
WEB模擬試験
・MacBook

参考書は3週。9割正解できるまで復習しました。
WEB模擬試験は、2回受験。参考書より難しい印象を受けました。
MacBookは、PHPのインストールが不要だったのでさっと動作確認したいときに便利でした。

受験申し込み

  1. Odyssey IDの登録
  2. 試験会場の申し込み

受験するには、Odyssey IDを使ってログインする必要があります。
登録はこちらから

試験会場の申し込みは、個人で行ないます。
試験会場は検索が可能なので、自宅近くで探しました。
(コロナ禍で受けられるスペースが少なく、探すのには苦労しました・・・。)

当日

写真付きの身分証明書が必要です。
別試験で運転免許証を筆箱の中に置いてたので忘れそうになりました汗

結果

900/1000(結果:合格)となりました。※700以上で合格

試験時間は60分でしたが、40分で解き終わりました。
難易度は、参考書 < 試験 ≦ 模擬試験 くらいに感じました。

感想

PHP初心者向けでよく構成されているなと感じました。
理不尽な問題が出題されるわけではなく、きちんと勉強した分取れる問題で良かったです。

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

※学習用メモ デザインパターン:Observer編[PHP]

Observerパターンとはどういうものか

Observerとは観察者という意味です。

通知するオブジェクト側が、通知されるオブジェクト側に観察(observe)される形になる事から、こう呼ばれる。(wikipediaより)

観察対象のオブジェクトが、特定のイベント(事象)を観察者側のオブジェクトに通知を行うことで処置が発生するという形となります。
そのため、観察対象のオブジェクトの状態が変化した際に、その状態変化に応じた処理を記述する必要がある場合に有効なパターンです。

別名出版(publication)-購読型(subscription)モデルとも呼ばれています。

登場するクラス

・抽象クラス
 Subjectクラス:観察対象の抽象クラス
 Observerクラス:観察者の抽象クラス
・具象クラス
 ConcreteSubjectクラス:観察対象の具象クラス
 ConcreteObserverクラス:観察者の具象クラス

Push型とPull型

Subject-Observer間の通知の方法には、Push型とPull型の2種類があります。

Push型:状態変化時にSubjectがObserverに通知する
通知された際に実行されるメソッドの引数の状態が変更されているため、直接その状態を知ることができます。
しかしそのメソッドに引数を取る形でインターフェースでの定義を行う必要があり、一般化に向きません。

Pull型:状態変化時にObserverがSubjectに状態を問い合わせる
こちらは通知された際に実行されるメソッドの引数を必要としないため一般化した形で定義が可能ですが、
通知の際の状態変化をSubjectに問い合わせる必要があるため、Subjectの構造を知っておく必要があります。

Observerパターンのメリット

・メリット
観察対象と観察者のクラスに小分けすることにより処理間の依存度が低くなる

・デメリット
Observer(観察者)に対しさらにObserverを数珠つなぎのようにつなげ、連鎖的に処理を行う場合ロジックの見通しが悪くなる場合がある

[参考]
https://ja.wikipedia.org/wiki/Observer_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3#:~:text=Observer%20%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%EF%BC%88%E3%82%AA%E3%83%96%E3%82%B6%E3%83%BC%E3%83%90%E3%83%BB%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%EF%BC%89,%E3%82%8F%E3%82%8C%E3%82%8B%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%E3%81%AE%E4%B8%80%E7%A8%AE%E3%80%82
https://qiita.com/shoheiyokoyama/items/d4b844ed29f84a80795b
https://www.techscore.com/tech/DesignPattern/Observer.html
https://www.ritolab.com/entry/138
https://qiita.com/kingconyd/items/fc16496d5b8eced5f7ab
https://www.ryotaku.com/entry/2019/07/25/000000#%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88%E3%81%A8%E3%83%87%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88
http://varmil.hateblo.jp/entry/2015/04/14/131322
https://www.ogis-ri.co.jp/otc/hiroba/technical/DesignPatternsWithExample/chapter05.html
http://hamasyou.com/blog/2004/07/25/observer/

サンプルコード

仕様

・今回はPush型で作成します。
・数字をランダムに生成し、10を超えたとき都度メール送信とツイッター投稿を行う(今回は文字での表示のみ)

観察対象のインターフェース(Subject)

SubjectInterface.php
/**
 * Subjectインターフェース
 */
interface SubjectInterface
{
    // Observerの追加
    public function addObserver($obj);
    // Observerの削除
    public function removeObserver($obj);
    // Observerへの通知
    public function notify();
}

観察者のインターフェース(Observer)

ObserverInterface.php
/**
 * Observerインターフェース
 */
interface ObserverInterface
{
    public function execute($obj);
}

ランダムに数字を生成し、Observerに通知を行う具象クラス(ConcreteSubject)

RondomNumberGenerate.class.php
class RondomNumberGenerate implements SubjectInterface
{
    private $num = 0;
    // Observer配列
    private $observerArray = array();

    private $notifyArray = array();

    public function getNumber()
    {
        return $this->num;
    }

    public function generateNumberAndNotify()
    {
        $this->num = mt_rand(0,20);
        if ($this->num >= 10) {
            $this->notify();
        }

        return $this->notifyArray;
    }

    public function addObserver($obj)
    {
        $this->observerArray[get_class($obj)] = $obj;
    }

    public function removeObserver($obj)
    {
        unset($this->observerArray[get_class($obj)]);
    }

    public function notify()
    {
        foreach ($this->observerArray as $observer) {
            $this->notifyArray[] = $observer->execute($this);
        }
    }
}

通知を受け取った場合に処理を行う観察者の具象クラス(ConcreteObserver)

mailNotification.class.php
class mailNotification implements ObserverInterface
{
    public function execute($obj)
    {
        return "メール通知:数字が" . $obj->getNumber() . "を超えました。";
    }
}
twitterNotification.class.php
class twitterNotification implements ObserverInterface
{
    public function execute($obj)
    {
        return "twitter:数字が" . $obj->getNumber() . "を超えました。";
    }
}

上記でランダムに数字が生成された際、それぞれの観察者(Observer)に通知され処理が行われる形となります。

使ってみる

$subject = new RondomNumberGenerate();

$subject->addObserver(new mailNotification());
$subject->addObserver(new twitterNotification());

print_r($subject->generateNumberAndNotify());

/*結果
Array
(
[0] => メール通知:数字が10を超えました。
[1] => twitter:数字が10を超えました。
)
*/

まとめ

・Observerパターンは、観察対象のオブジェクトの状態が変化した際に、その状態変化に応じた処理を記述する必要がある場合に有効なパターンである。
・今回はPush型でサンプルコードを記述しましたが、Pull型との適切な使い分けが重要となるように感じました。

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

【Symfony】Migrationコマンドまとめ

よく忘れるので自分用にメモしておく。
以下コマンドの前につけるphp bin/consoleは省略する。

1. 事前準備

.envファイルを設定する
/.env
DATABASE_URL="mysql://root:root@db:3306/db_name?serverVersion=13&charset=utf8"
DB作成

以下のコマンドを使うと .env の接続設定で指定した db_name のデータベースを作成する。

$ doctrine:database:create
collateの文字コードを繋いでるDBに合わせる
/config/packages/doctrine.yaml
default_table_options:
            collate: utf8_unicode_ci
Status表示

二つのコマンドが用意されているが、正直どっちを使ってもいいと思う。

$ doctrine:migrations:status
$ doctrine:migrations:list

個人的にはlistの方が簡潔で、かつgetDescriptionメソッドで返している値も表示しれくれるのでよく使っている。

2. 生成

方法1

現在のentityとdatabaseの状態を比べて差分のsqlのmigrationsファイルを生成

doctrine:migrations:diff
方法2

空のmigrationsファイルを生成

doctrine:migrations:generate

3. 実行

1. 全て実行
doctrine:migrations:migrate
2. ファイル指定して実行

ファイル指定してdownもできる

doctrine:migrations:excute --up 'DoctrineMigrations\Version20210103062456'
doctrine:migrations:excute --down 'DoctrineMigrations\Version20210103062456'

--dry-runオプションで実行されるSQL文が確認できるはずだが、なぜか確認されない。。。。
あとで時間ある際に調査、、、、

4. Tips

マイグレーションを実行せずに履歴情報のみを変更する。
doctrine:migrations:version {バージョン} (--add|--delete)
実行確認を出させないオプション

複数行のコマンドを一気に流す時によくつけるオプション

--no-interaction
全てのmigrationsファイルの状態を確認
doctrine:migrations:status --show-versions

*このオプションは最近のバージョンではなくなっているが、doctrine:migrations:listコマンドが対応していないバージョンではすごく助けられていた。

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

Tinkerを使ってみYO!

はじめに

Laravelを学ぶ中でTinkerが便利だなと感じたので記事にしてみました!
TinkerはLaravel標準で使える対話型のコマンドラインツールで、「PHP/Laravelの関数を実行」「DBのレコード操作」などを行うことができます。今回はユーザデータを見たり消したりしてみようと思います。

目次

  1. Tinkerを起動
  2. ユーザデータ確認
  3. ユーザデータ削除
  4. まとめ

$ php artisan tinker

dockerの場合は、docker-compose.ymlファイルある場所で下記コマンド実行
$ docker-compose exec コンテナ名 php artisan tinker

を実行すると下記のような表示がされ、対話的にコマンドを実行することができます。

>>> 

使用できるコマンドを確認できます。

>>> help

次はユーザーモデル全件を確認

>>> App\User::all();

Userクラスを使っていますが、クラスを使う場合はApp\Userといったように名前空間も記述するようにしてください。

名前空間とは、app/User.phpのnamespaceの後に記述されている部分です。

└──laravel
    └── app
        └── User.php

ちなみにこのappディレクトリはアプリケーションのコアとなるコードを含む重要なディレクトリです。
アプリケーション内のクラスのほとんどはこの中に設置されます。

このメソッドは下記のようにコレクション(Illuminate\Database\Eloquent\Collection)が帰ってきました。このようにメソッドが配列を返すか、コレクションを返すかといったことも確認できます。

your.terminal
=> Illuminate\Database\Eloquent\Collection {#3053
     all: [
       App\User {#3978
         id: 1,
         name: "hoge",
         email: "hoge@example.com",
         email_verified_at: null,
         created_at: "2021-01-03 23:10:40",
         updated_at: "2021-01-03 23:10:40",
       },
     ],
   }

なお、App\Userを確認していただくとわかりますが、passwordやremember_tokenといったプロパティがあるのに、それらが表示されていません。(Userモデルで表示しない指定がされているため)

ユーザーモデルを削除
次にhogeというユーザーが不要なので、これを削除します。

ユーザーhogeのidは1であるので、以下のコードを実行してください。

your.terminal
>>> App\User::destroy(1); 

=> 1 といったように削除件数が表示されれば削除は成功です。

TinkerはLaravel標準で使える対話型のコマンドラインツールで、「PHP/Laravelの関数を実行」「DBのレコード操作」などを行うことができます。使いこなせるように積極的に使用していきます。

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

Tinkerでデータ操作してみました

はじめに

Laravelを学ぶ中でTinkerが便利だなと感じたので記事にしてみました!
TinkerはLaravel標準で使える対話型のコマンドラインツールで、「PHP/Laravelの関数を実行」「DBのレコード操作」などを行うことができます。今回はユーザデータを見たり消したりしてみようと思います。

Tinkerを起動

$ php artisan tinker

dockerの場合は、docker-compose.ymlファイルある場所で下記コマンド実行
$ docker-compose exec コンテナ名 php artisan tinker

を実行すると下記のような表示がされ、対話的にコマンドを実行できます。

>>> 

下記コマンド(help)で使用できるコマンドを確認できます。

>>> help

ユーザーモデル全件を確認

>>> App\User::all();

Userクラスを使っていますが、クラスを使う場合はApp\Userといったように名前空間も記述するようにしてください。

名前空間とは、app/User.phpのnamespaceの後に記述されている部分です。

└──laravel
    └── app
        └── User.php

ちなみにこのappディレクトリはアプリケーションのコアとなるコードを含む重要なディレクトリです。
アプリケーション内のクラスのほとんどはこの中に設置されます。

この all() メソッドからは下記のようにコレクション(Illuminate\Database\Eloquent\Collection)が帰ってきました。このようにメソッドがどんな値を返すか、配列かコレクションかといったようなことも確認できます。

your.terminal
=> Illuminate\Database\Eloquent\Collection {#3053
     all: [
       App\User {#3978
         id: 1,
         name: "hoge",
         email: "hoge@example.com",
         email_verified_at: null,
         created_at: "2021-01-03 23:10:40",
         updated_at: "2021-01-03 23:10:40",
       },
     ],
   }

なお、App\Userを確認していただくとわかりますが、passwordやremember_tokenといったプロパティがあるのに、それらが表示されていません。(Userモデルで表示しない指定がされているため)

ユーザーモデルを削除

次にhogeというユーザーが不要なので、これを削除します。

ユーザーhogeのidは1であるので、以下のコードを実行してください。

your.terminal
>>> App\User::destroy(1); 

=> 1 といったように削除件数が表示されれば削除は成功です。

まとめ

TinkerはLaravel標準で使える対話型のコマンドラインツールで、「PHP/Laravelの関数を実行」「DBのレコード操作」などを行うことができます。使いこなせるように積極的に使用していきます。

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

Tinkerでデータ操作

はじめに

Laravelを学ぶ中でTinkerが便利だなと感じたので記事にしてみました!
TinkerはLaravel標準で使える対話型のコマンドラインツールで、「PHP/Laravelの関数を実行」「DBのレコード操作」などを行うことができます。今回はユーザデータを見たり消したりしてみようと思います。

Tinkerを起動

$ php artisan tinker

dockerの場合は、docker-compose.ymlファイルある場所で下記コマンド実行
$ docker-compose exec コンテナ名 php artisan tinker

を実行すると下記のような表示がされ、対話的にコマンドを実行できます。

>>> 

下記コマンド(help)で使用できるコマンドを確認できます。

>>> help

ユーザーデータ全件を確認

>>> App\User::all();

Userクラスを使っていますが、クラスを使う場合はApp\Userといったように名前空間も記述するようにしてください。

名前空間とは、app/User.phpのnamespaceの後に記述されている部分です。

└──laravel
    └── app
        └── User.php

ちなみにこのappディレクトリはアプリケーションのコアとなるコードを含む重要なディレクトリです。
アプリケーション内のクラスのほとんどはこの中に設置されます。

この all() メソッドからは下記のようにコレクション(Illuminate\Database\Eloquent\Collection)が帰ってきました。このようにメソッドがどんな値を返すか、配列かコレクションかといったようなことも確認できます。

your.terminal
=> Illuminate\Database\Eloquent\Collection {#3053
     all: [
       App\User {#3978
         id: 1,
         name: "hoge",
         email: "hoge@example.com",
         email_verified_at: null,
         created_at: "2021-01-03 23:10:40",
         updated_at: "2021-01-03 23:10:40",
       },
     ],
   }

なお、App\Userを確認していただくとわかりますが、passwordやremember_tokenといったプロパティがあるのに、それらが表示されていません。(Userモデルで表示しない指定がされているため)

ユーザーデータを削除

次にhogeというユーザーが不要なので、これをデータベースから削除します。

ユーザーhogeのidは1であるので、以下のコードを実行してください。

your.terminal
>>> App\User::destroy(1); 

=> 1 といったように削除件数が表示されれば削除は成功です。

まとめ

今回は初歩的な操作としてユーザデータを見たり消したりしました。
TinkerはLaravel標準で使える対話型のコマンドラインツールで、「PHP/Laravelの関数を実行」「DBのレコード操作」などを行うことができます。使いこなせるように積極的に使用していきたいです。

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

MAMPでのtimezone設定

はじめまして

プログラミングの初学者ですが、学習する際につまずいたことや間違えてしまったことなどを、Qiitaにまとめていこうと思います。

同じように失敗してしまう方の参考になれば幸いです。

PHP+MySQL入門講座

プログラミングの学習には、主にUdemy を使用しています。最近では、たにぐちまことさん(@seltzer)の動画を見てPHP やMySQL、WordPressについて学習しています。

初めに、WordPress開発マスター講座を受講し、初学者にも分かりやすく説明が丁寧でしたので、別の講座も受講することにしました。それが、PHP+MySQL(MariaDB) Webサーバーサイドプログラミング入門講座です。

講師がWindows OS

前回の講座は2020年12月31日にやり終えました。なので、こちらの講座はちょうど新年の元日から開始する事ができ、とても気合が入っていました。講師のたにぐちまことさんは、windowsを使用されており、私のMacとは操作が少し違う点、気にはなっておりましたが、前回は問題なかったため、今回も大丈夫だろうと思っていました。

XAMPP?MAMP?

受講に伴い開発環境を整えることになりましたが、WindowsはXAMPP、MacはMAMPというソフトがあり、講座はXAMPPの方で進めていくとの事。(えー大丈夫かな。)そして開始早々にもう完全に見た目が違う。

▼XAMPP
XAMPP.png

▼MAMP
MAMP.png
そして、ついに躓きました。

現在の時刻を表示する

date関数を使用して、現在の時刻を表示する内容です。

print(date('G時 i分 s秒'));

ブラウザでは以下のように出力します。
時刻.png
説明によると、タイムゾーンの設定が違うため、現時刻が表示されないとの事でした。php.iniファイルの内容を修正する必要があるのですが、ここからの説明は全てXAMPPで進みます。

講座の中では、MAMPでphp.iniファイルを操作する説明がありませんでしたので、そもそもファイルのパスがわからず検索しました。
参考サイト:php.iniファイルの場所/PHPのバージョン確認・変更 - MAMPの使い方

表示されている時刻が修正されない!

ファイル内を「date.timezone 」で検索し、以下のように修正するだけの簡単な作業でした。

date.timezone = "Europe/Berlin"

date.timezone = "Asia/Tokyo"

しかし反映されない!9時間ずれている!なぜ?

このまま放置して講座進めれば解決するかもと思いましたが、そんな気配はない。色々見直したりして結局30分くらい経過してしまいました。

実際のvscodeのキャプチャがこちら。
date.png
最終行がコメントアウトされていますね。
このせいで変更した設定が反映されていませんでした。
みなさんご注意ください(´・Д・)」

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

【PHP】標準入力からの繰り返し【メモ】

自分用のメモです。

paizaのPHP基礎問題。

<?php
function getNamesFromStdin() {
    $names = [];
    $input = trim(fgets(STDIN));
    while ($input) {
        $names[] = $input;
        $input = trim(fgets(STDIN));
    }
    return $names;
}
// 関数を定義
function main() {
    $names = getNamesFromStdin();
    foreach ($names as $name) {
        echo $name."さん\n";
    }
}
// 関数を実行
main();

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

Laravel6系でゲストログイン機能(かんたんログイン)の実装

はじめに

本記事では、ログイン時にユーザー名パスワードを入力しなくても、
ワンクリックでゲストユーザーとしてログインできる「ゲストログイン機能」の実装方法をご紹介します。

転職活動用のポートフォリオ等にゲストログイン機能があると
多忙な採用担当の方の負担が減り、アプリを利用してもらえる確率が高まりますよ!

事前準備

ゲストログイン機能の実装の前に、以下が予め準備されていることが前提となります。

①ログイン機能の実装されている
②ゲストログイン用のユーザー(ゲストユーザー)の登録されている
 → 例)ユーザー名:ゲストユーザー
     メールアドレス:guest@guest.com
③DBにusersテーブルがあり、passwordカラムが定義されている
④Userモデルが作成されている

実装内容

ログイン機能とゲストユーザーの準備ができたら、いよいよ実装に入ります。
ご紹介する実装内容は割と簡単で、以下になります。

  • ゲストログインの処理を記述(Controller)

  • ルーティングの設定(web.php等)

  • ログイン画面等、任意のページにゲストログイン用のリンクを設置する(View)

  • 補足)ユーザープロフィール編集機能の制限

ゲストログインの処理を記述(Controller)

まず今回は、Laravel6系をインストールしたら初めから存在する
app/Http/Controllers/Auth/LoginController.php
に、ゲストログインの処理を書いていきます。

LoginController.php
<?php

namespace App\Http\Controllers\Auth;

use App\Models\User; //※ ModelsディレクトリにUser.phpがある場合
use Illuminate\Support\Facades\Auth;
# 〜〜〜〜〜〜〜 略 〜〜〜〜〜〜〜

class LoginController extends Controller
{

  # 〜〜〜〜〜〜〜 略 〜〜〜〜〜〜〜

    // ゲストユーザー用のメールアドレスを定数として定義
    private const GUEST_USER_EMAIL = 'guest@guest.com';

    // ゲストログイン処理
    public function guestLogin()
    {
        $user = User::where('email', self::GUEST_USER_EMAIL)->first();
        if ($user) {
            Auth::login($user);
            return redirect('/');
        }
        return redirect('/');
    }

}

解説〜

まず、以下のようにゲストユーザー用のメールアドレス'guest@guest.com'を定数として定義しています。

LoginController.php
private const GUEST_USER_EMAIL = 'guest@guest.com';

今回の実装では、このメールアドレス情報の文字列を利用して、
DBにゲストユーザーの登録情報があるかを探します。

LoginController.php
public function guestLogin()
    {
        $user = User::where('email', self::GUEST_USER_EMAIL)->first();
        if ($user) {
            Auth::login($user);
            return redirect('/');
        }

処理の流れとしては、
「もしメールアドレスが'guest@guest.com'であるユーザー(ゲストユーザー)がDBに存在すれば、そのユーザーでログインをしてトップページ('/')にリダイレクトする」
ようになっています。

ルーティングの設定(web.php等)

次に、今回は
routes/web.php
を編集して、ゲストログイン機能用のルーティングを設定します。
以下を追記してください。

web.php
# ゲストユーザーログイン
Route::get('guest', 'Auth\LoginController@guestLogin')->name('login.guest');

任意のページにゲストログイン用のリンクを設置する(View)

続いて、ゲストログイン処理をブラウザから実行できるように「ゲスト
ログイン」ボタンをViewに追加します。

ボタンをお好みの場所に設置していきましょう。

ちなみに私は、自作アプリには以下のように
ヘッダーと、ログイン画面の2箇所に設置しました。

・ヘッダーのナビゲーションバー(右上)
スクリーンショット 2021-01-02 21.21.55.png

・ログイン画面(右下)
スクリーンショット 2021-01-02 21.20.33.png

それでは実装に入ります。
以下のコードをView(Blade)の任意の場所に追記してください。(デザインはBootstrap使用)

<button class="btn btn-success">
  <a href="{{ route('login.guest') }}" class="text-white">
    ゲストログイン
  </a>
</button>

リンク先のroute('login.guest')は、先ほどweb.phpでルーティングを設定した
name('login.guest')とリンクしています。
また、< button >タグや< a >タグ内のclassには、Bootstrapのデザインを使用しています。
この辺については、お好みで調整してください。

〜〜〜〜〜〜〜〜〜〜〜

ここまで実装できたら、ゲストログイン機能の実装自体は完了です!
実際に「ゲストログイン」ボタンをクリックして、ゲストユーザーとしてログインできているか確認してみましょう!

ゲストユーザーのプロフィール編集を制限する

最後に、セキュリティ的な対策です。
もしあなたの開発しているアプリに、ユーザーの名前メールアドレス等を編集できる
「ユーザープロフィール編集機能」がある場合、
ゲストユーザーのメールアドレスは変更できないようにしてください。

もし誰かにゲストユーザーのメールアドレスを変更されてしまった場合、ゲストログイン機能が動かなくなってしまいます^^;
そのため、以下のような対策を取る必要があります

  • (A)メールアドレスの変更の際に、パスワードを求める仕様にする

  • (B)ゲストユーザーでログイン時はメールアドレスを変更できない仕様にする

  • (C)そもそもどのユーザーでもメールアドレスは変更できない仕様にする

私の場合、(B)で実装しましたので、簡単にその実装方法をご紹介したいと思います。

実装例

フロントエンド側の対策

スクリーンショット 2021-01-03 10.38.31.png

プロフィール編集画面にて、ゲストユーザーでログインしている時は
ユーザー名パスワードを編集できないようにしています。
これは、タグにreadonly属性を追加することで実現できます。

実装例

@if (Auth::user()->email == 'guest@guest.com')
  <p class="text-danger">※ゲストユーザーは、ユーザー名とメールアドレスを編集できません。</p>
@endif
<div class="form-group">
  <label for="name">
    ユーザー名
    <small class="blue-grey-text">(15文字以内)</small>
  </label>
  @if (Auth::user()->email == 'guest@guest.com')
    <input class="form-control" type="text" id="name" name="name" value="{{ $user->name }}" readonly>
  @else
    <input class="form-control" type="text" id="name" name="name" value="{{ $user->name ?? old('name') }}">
  @endif
</div>
<div class="form-group">
  <label for="email">メールアドレス</label>
  @if (Auth::user()->email == 'guest@guest.com')
    <input class="form-control" type="text" id="email" name="email" value="{{ $user->email }}" readonly>
  @else
    <input class="form-control" type="text" id="email" name="email" value="{{ $user->email ?? old('email') }}">
  @endif
</div>

上記のコードでは、

@if (Auth::user()->email == 'guest@guest.com')

でゲストユーザーでログインしているかを判定し、もしゲストユーザーでログインしている場合には
< input >タグにreadonly属性を付与するようにしています。
また、デザインにはBootstrapを使用しています。

ただし、この実装だけでは重大な欠点がありまして、Chrome等のデペロッパーツールを使用して
readonly属性を消されると簡単にメールアドレスを入力できてしまい、上書きされてしまいます。
なのでバックエンド側での対策も必要になってきます。

バックエンド側の対策

私の場合、ユーザープロフィール編集機能の実装には以下の2つを作成しています。
・UserController.php
・UserRequest.php(フォームリクエストによるバリデーション)

実装の手順としては、次の2つです。

①User.Request側:ゲストユーザーがログインしている場合は、ユーザー名メールアドレスをバリデーションにかけないようにする
②UserController側:バリデーションにかけた値だけをDBに保存するようにする

これで、ゲストユーザーのプロフィール編集ができないようになります。

実装例

UserRequest.php
private const GUEST_USER_EMAIL = 'guest@guest.com';

public function rules()
{
    // ゲストユーザーログイン時は、ユーザー名とメールアドレスをバリデーションにかけない
    if (Auth::user()->email == self::GUEST_USER_EMAIL) {
        return [
            'profile_image' => 'file|mimes:jpeg,png,jpg,bmb|max:2048',
            'self_introduction' => 'string|max:200|nullable',
        ];
    } else { // ゲストユーザー以外がログインしている時は、全てのユーザー情報をバリデーションにかける
        return [
            'name' => 'required|string|max:15|',
            'email' => 'required|string|email|max:255|',
            'profile_image' => 'file|mimes:jpeg,png,jpg,bmb|max:2048',
            'self_introduction' => 'string|max:200|nullable',
        ];
    }
}
UserController.php
public function update(UserRequest $request, string $name)
{
    $user = User::where('name', $name)->first();

    // バリデーションにかけた値だけをDBに保存
    $user->fill($request->validated())->save();

    return redirect()->route('users.show',['name' => $user->name]);
}

以上で、ゲストログイン機能の実装は終了になります!
お疲れ様でした!

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