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

Wordpressで複合検索(絞り込み検索)をプラグインなしで実装する方法

Wordpressのデフォルトではキーワード検索しかできないため、カテゴリやカスタムフィールドなどで絞り込むことができない。

複合検索(絞り込み検索)を実装するには独自にフォームを作ってクエリを実行する。

複合検索のためのフォームを実装する

検索フォームはHTMLで自由に作れるので、検索したい項目を作成すればOK。

ここでは、検索キーワード、カテゴリ、カラーというカスタムフィールドを検索する。

nameに入れる文字はクエリの作成で使うのでわかりやすい文字を入れておく。

フォームは、search-form.phpのようなテンプレートとして作って、サイドバーなど好きなところから呼び出す。

// search-form.php
<form method="get" id="searchform" action="<?php echo esc_url( home_url( '/' ) ); ?>">
    <label for="s">キーワード</label>
    <input type="text" class="field" name="s" id="s" placeholder="検索キーワード" />
    <label for="category">カテゴリ</label>
    <select name="category" id="category">
        <?php 
            $cateogry_list = get_terms( array(
                'taxonomy' => 'category',
            ) );
            foreach( $cateogry_list as $category ) {
                if( $category->parent == 0 ) {
                        echo '<option value="'. esc_attr( $category->term_id ) .'">
                                        '. esc_html( $category->name ) .'</option>';
                }
            }
        ?>
    </select>
    <label for="s">カラー</label>
    <select name="color" id="color">
        <option value="red">Red</option>
        <option value="blue">Blue</option>
        <option value="yellow">Yellow</option>
    </select>
    <input type="submit" class="submit" value="検索" />
</form>

複合検索の表示方法

検索結果を表示するには、WP_Queryにクエリを渡すだけ。

クエリを作るために必要な検索キーワードや選択項目を取得するには$_GET['name']を使う。

nameの部分にはフォームのname属性に入れた文字を入れる。namesなら$_GET['s']となる。

// search.php
<?php
$args = array(
  's' =>  $_GET['s'], // 検索ワード
  'cat' => $_GET['category'], // カテゴリID
  'meta_query'  =>  array(
    array(
      'key'     => 'color', // メタキー
      'value'   => $_GET['color'], // 値
    ),
  )
);
$wp_query = new WP_Query( $args ); ?>

<?php 
if ( $wp_query->have_posts() ) : 

  /* Start the Loop */
  while ( $wp_query->have_posts() ) :
    $wp_query->the_post();

    // 投稿内容を表示

  endwhile;

    // ループ終了後の処理、ページネーション表示など
    the_posts_navigation();

else :

  // 投稿がない場合の表示

endif;
?>

検索結果の表示はpre_get_postsを使う方法もあるのでどちらか好きな方で実装する。

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

PHPでRSSを読み込む方法

初めに

プログラミング学習を独学で続けてきて、これは忘れそうだなと思いましたので、備忘録として記載します。

RSSとは

RSSは「Rich Site Summary(リッチ・サイト・サマリー)」の略です。
ブログ等でこのようなアイコンを見たことがあると思います。
スクリーンショット 2020-03-22 23.25.15.png
これがRSSのマークです!!

では、このRSSは何をする時に使うのでしょうか?

続きは後日書きます。

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

ElixirでもPHP的に気軽なWebアプリ開発をしたい

fukuoka.ex/kokura.exのpiacereです
ご覧いただいて、ありがとうございます :bow:

Phoenixは、Rails同様、MVCやルーティングによるWebアプリ構築を前提に置いているため、PHPのような気軽さでWebアプリを作るのに向いていないと思われがちです

他にも、「Phoenixを使わず、薄いWebサーバを使いたい」といった声を聞くことがありますが、よくよく聞いてみると、ステータスコードをキメ細かく調整したり、サーバプロセスを自前で起動したい等の本来的な薄いWebサーバを必要としている訳では無く、「MVCを使いたくない」「ルーティングを書きたくない」といったレベルの要望だったりすることも多いです

そこで、Phoenixのルーティングやリクエストハンドラーを司る「Plug」の柔軟性を用いれば、PHPと同様の気軽さでWebアプリ開発することもできる … ということを実感していただくためにコラムを書きました

本コラムの検証環境

本コラムは、以下環境で検証しています(Windowsで実施していますが、Linuxやmacでも動作する想定です)

なおPhoenixは、1.3系でも動作します(ElixirもPhoenixのバージョンに準じた古いものでも大丈夫です)

事前準備:Phoenix PJを作成する

まずPhoenix PJを作成し、Phoenixを起動します

mix phx.new basic --no-ecto
…
Fetch and install dependencies? [Yn] 【Yを入力してEnter】
cd basic
iex -S mix phx.server

なおチョイ技として、上記Yで走るmix deps.getとnpm installのうち、npm installが遅いので、コンソールを2枚立てて、裏で走らせると、構築を待たずに先に進むこともできます

1枚目のコンソール
mix phx.new basic --no-ecto
…
Fetch and install dependencies? [Yn] 【Nを入力してEnter】
cd basic
mix deps.get
iex -S mix phx.server
2枚目のコンソール
cd 【上記Phoenix PJを作った元のフォルダ】
cd basic/asset
npm install

ブラウザで「http://localhost:4000」にアクセスすると、Phoenixで作られたデフォルトのWebページが見れます
image.png

PHPのような気軽さでWebアプリを作るには?

Phoenixが前提としている「特定MVCへのルーティング」をhtml.eexファイル名へと自動マッピングし、mix phx.newで生成されたコントローラがデフォルトで素通ししていないGETパラメータを素通しすればOKです

なお、html.eexファイル名へのマッピング時、複数階層のフォルダもマッピング可能とすることで、サブフォルダ配下のhtml.eexファイルにも気軽にアクセスできるようになります

手順①:html.eexの表示をルーティング不要に

まずはルーティングで、自動マッピングのために、「*_ path」を指定することで、フォルダ階層に関係無いパスを「_path」パラメータで取得できるようになります

「_path」パラメータには、URLに指定されたフォルダ階層が、リストで入ってきます(たとえば、abc/defというパスであれば、[ "abc", "def" ]という感じで)

lib/basic_web/router.ex
defmodule BasicWeb.Router do

  scope "/", BasicWeb do
    pipe_through :browser

    get "/", PageController, :index
    get "/*_path", PageController, :index  # <-- add here

手順②:html.eexの複数階層フォルダ指定可能に

render()の第2引数に指定するパスは、デフォルトだと複数階層を指定できないので、Viewにワイルドカード的なパスのパターン指定を追加すれば、複数階層を指定可能にできます

lib/basic_web.ex
defmodule BasicWeb do

  def view do
    quote do
      use Phoenix.View,
        pattern: "**/*",  # <-- add here
        root: "lib/basic_web/templates",
        namespace: BasicWeb, 

手順③:パスの自動マッピング、GETパラメータの素通し

PageControllerで、「_path」パラメータのフォルダ階層リストを、html.eexのパスにマッピングします

また、mix phx.newで生成されたコントローラが「_params」という感じでアンダースコアで未使用にしているので、これを解除し、renderの第3引数に渡すことで、GETパラメータを素通しさせます

lib/basic_web/controllers/page_controller.ex
defmodule BasicWeb.PageController do
  use BasicWeb, :controller

  def index( conn, params ) do
    page = if params[ "_path" ] == nil, do: "index.html", else: String.slice( Enum.reduce( params[ "_path" ], "", & "#{ &2 }#{ &1 }/" ), 0..-2 ) <> ".html"
    render( conn, page, params: params )
  end
end

手順④:html.eexページを追加する

1)templates/page直下にhtml.eex追加

これで、page配下にhtml.eexを追加するだけで、新たなページが追加可能となったので、追加してみます

lib/basic_web/templates/page/another.html.eex
<h1>This is another page</h1>

<%= inspect( System.build_info() ) %>
<hr>
<%= inspect( @params ) %>

ブラウザで「http://localhost:4000/another」にアクセスすると、以下のようなページが表示されます
image.png

URLにパラメータを指定してアクセスすると、@ paramsパラメータに入るので、GET処理も気軽に使えるようになります
image.png

これで、ルーティングやMVCを追加せずとも、PHPのように、新たなページ追加やGETパラメータ処理できるようになりました

2)templates/page配下のサブフォルダ下にhtml.eex追加

pageフォルダ配下にフォルダを掘って、その配下にページを配置しても動きます

「abc」というフォルダを掘り、「def」というページを追加します

lib/basic_web/templates/page/abc/def.html.eex
<h1>I'm def page</h1>

<%= inspect( @params ) %>

ブラウザで「http://localhost:4000/abc/def」にアクセスすると、以下のようなページが表示されます(「_path」パラメータにフォルダ階層がリストで入っていることも確認できます)
image.png

3)GETフォーム処理も気軽に書く

GETパラメータが通るようになっているので、formでフォーム処理も気軽に書けます

lib/basic_web/templates/page/abc/form.html.eex
<h1>I'm form page</h1>

<form method="GET" action="/abc/def">

<p>
メモ:
<input name="memo" type="text">
</p>

<p>
好きな言語:
<input name="language[]" type="checkbox" value="Elixir">Elixir
<input name="language[]" type="checkbox" value="Rust">Rust
<input name="language[]" type="checkbox" value="Julia">Julia
</p>

<p>
年代:
<select name="age">
  <option value="10">10代
  <option value="20">20代
  <option value="30">30代
  <option value="30">40代
  <option value="50">50代
  <option value="60">60代
  <option value="70">70代以上
</select>
</p>

<input type="submit" value="送信">

</form>

ブラウザで「http://localhost:4000/abc/form」にアクセスすると、以下のようなページが表示されます
image.png

「送信」ボタンを押下すると、入力されたパラメータが表示されますが、def.html.eexでやっているように、@ paramsの中身を使ってフォーム処理を行うことができます
image.png

4)POSTフォーム処理も気軽に書く

POSTパラメータによるフォーム処理に対応するには、ルーティングにPOSTの分も書きます

lib/basic_web/router.ex
defmodule BasicWeb.Router do

  scope "/", BasicWeb do

    post "/*_path", PageController, :index  # <-- add here

formのmethodをPOSTに変更します

lib/basic_web/templates/page/abc/form.html.eex
<h1>I'm form page</h1>

<form method="POST" action="/abc/def">
              ^-- modify here
…

ブラウザで「http://localhost:4000/abc/form」にアクセスし、「送信」ボタンを押下すると、GET同様、POSTでも入力されたパラメータが表示されるようになります(GETのときはURLにパラメータがあったのに対し、POSTでは無くなっています)
image.png

終わり

Phoenixで、PHPみたいに気軽なWebアプリ開発をする手順を紹介しました

Phoenixは、ルーティングとMVCを前提にしていますが、Plugの機能とコントローラでのパラメータ処理を併用すれば、このような気軽さを導入することもできます

無論、ルーティングやMVCは、理由があって採用されている面もあるので、そのメリット/デメリットはちゃんと学んだり、理解しつつ、一方で、このテクニックを使って、簡単なWebアプリ構築もお楽しみください

p.s.このコラムが、面白かったり、役に立ったら…

image.pngimage.png にて、どうぞ応援よろしくお願いします:bow:

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

ElixirでもPHP的に気軽なWebアプリ開発をしたい(Phoenixより薄いWebFW実現?)

fukuoka.ex/kokura.exのpiacereです
ご覧いただいて、ありがとうございます :bow:

Phoenixは、Rails同様、MVC※やルーティングによるWebアプリ構築を前提に置いているため、PHPのような気軽さでWebアプリを作るのに向いていないと思われがちです
※正確には、Phoenixのソレは、MVCのようで、MVCではありません

他にも、「Phoenixを使わず、薄いWebサーバを使いたい」といった声を聞くことがありますが、よくよく聞いてみると、ステータスコードをキメ細かく調整したり、サーバプロセスを自前で起動したい等の本来的な薄いWebサーバを必要としている訳では無く、「MVCを使いたくない」「ルーティングを書きたくない」といったレベルの要望だったりすることも多いです

そこで、Phoenixのルーティングやリクエストハンドラーを司る「Plug」の柔軟性を用いれば、PHPと同様の気軽さでWebアプリ開発することもできる … ということを実感していただくためにコラムを書きました

本コラムの検証環境

本コラムは、以下環境で検証しています(Windowsで実施していますが、Linuxやmacでも動作する想定です)

なおPhoenixは、1.3系でも動作します(ElixirもPhoenixのバージョンに準じた古いものでも大丈夫です)

事前準備:Phoenix PJを作成する

まずPhoenix PJを作成し、Phoenixを起動します

mix phx.new basic --no-ecto
…
Fetch and install dependencies? [Yn] 【Yを入力してEnter】
cd basic
iex -S mix phx.server

なおチョイ技として、上記Yで走るmix deps.getとnpm installのうち、npm installが遅いので、コンソールを2枚立てて、裏で走らせると、構築を待たずに先に進むこともできます

1枚目のコンソール
mix phx.new basic --no-ecto
…
Fetch and install dependencies? [Yn] 【Nを入力してEnter】
cd basic
mix deps.get
iex -S mix phx.server
2枚目のコンソール
cd 【上記Phoenix PJを作った元のフォルダ】
cd basic/asset
npm install

ブラウザで「http://localhost:4000」にアクセスすると、Phoenixで作られたデフォルトのWebページが見れます
image.png

PHPのような気軽さでWebアプリを作るには?

Phoenixが前提としている「特定MVCへのルーティング」をhtml.eexファイル名へと自動マッピングし、mix phx.newで生成されたコントローラがデフォルトで素通ししていないGETパラメータを素通しすればOKです

なお、html.eexファイル名へのマッピング時、複数階層のフォルダもマッピング可能とすることで、サブフォルダ配下のhtml.eexファイルにも気軽にアクセスできるようになります

手順①:html.eexの表示をルーティング不要に

まずはルーティングで、自動マッピングのために、「*_ path」を指定することで、フォルダ階層に関係無いパスを「_path」パラメータで取得できるようになります

「_path」パラメータには、URLに指定されたフォルダ階層が、リストで入ってきます(たとえば、abc/defというパスであれば、[ "abc", "def" ]という感じで)

lib/basic_web/router.ex
defmodule BasicWeb.Router do

  scope "/", BasicWeb do
    pipe_through :browser

    get "/", PageController, :index
    get "/*_path", PageController, :index  # <-- add here

手順②:html.eexの複数階層フォルダ指定可能に

render()の第2引数に指定するパスは、デフォルトだと複数階層を指定できないので、Viewにワイルドカード的なパスのパターン指定を追加すれば、複数階層を指定可能にできます

lib/basic_web.ex
defmodule BasicWeb do

  def view do
    quote do
      use Phoenix.View,
        pattern: "**/*",  # <-- add here
        root: "lib/basic_web/templates",
        namespace: BasicWeb, 

手順③:パスの自動マッピング、GETパラメータの素通し

PageControllerで、「_path」パラメータのフォルダ階層リストを、html.eexのパスにマッピングします

また、mix phx.newで生成されたコントローラが「_params」という感じでアンダースコアで未使用にしているので、これを解除し、renderの第3引数に渡すことで、GETパラメータを素通しさせます

lib/basic_web/controllers/page_controller.ex
defmodule BasicWeb.PageController do
  use BasicWeb, :controller

  def index( conn, params ) do
    page = if params[ "_path" ] == nil, do: "index.html", else: String.slice( Enum.reduce( params[ "_path" ], "", & "#{ &2 }#{ &1 }/" ), 0..-2 ) <> ".html"
    render( conn, page, params: params )
  end
end

手順④:html.eexページを追加する

1)templates/page直下にhtml.eex追加

これで、page配下にhtml.eexを追加するだけで、新たなページが追加可能となったので、追加してみます

lib/basic_web/templates/page/another.html.eex
<h1>This is another page</h1>

<%= inspect( System.build_info() ) %>
<hr>
<%= inspect( @params ) %>

ブラウザで「http://localhost:4000/another」にアクセスすると、以下のようなページが表示されます
image.png

URLにパラメータを指定してアクセスすると、@ paramsパラメータに入るので、GET処理も気軽に使えるようになります
image.png

これで、ルーティングやMVCを追加せずとも、PHPのように、新たなページ追加やGETパラメータ処理できるようになりました

2)templates/page配下のサブフォルダ下にhtml.eex追加

pageフォルダ配下にフォルダを掘って、その配下にページを配置しても動きます

「abc」というフォルダを掘り、「def」というページを追加します

lib/basic_web/templates/page/abc/def.html.eex
<h1>I'm def page</h1>

<%= inspect( @params ) %>

ブラウザで「http://localhost:4000/abc/def」にアクセスすると、以下のようなページが表示されます(「_path」パラメータにフォルダ階層がリストで入っていることも確認できます)
image.png

3)GETフォーム処理も気軽に書く

GETパラメータが通るようになっているので、formでフォーム処理も気軽に書けます

checkboxのような複数値を取るタイプには、nameの末尾に「[]」を置く点が、忘れてはならないポイントです

lib/basic_web/templates/page/abc/form.html.eex
<h1>I'm form page</h1>

<form method="GET" action="/abc/def">

<p>
メモ:
<input name="memo" type="text">
</p>

<p>
好きな言語:
<input name="language[]" type="checkbox" value="Elixir">Elixir
<input name="language[]" type="checkbox" value="Rust">Rust
<input name="language[]" type="checkbox" value="Julia">Julia
</p>

<p>
年代:
<select name="age">
  <option value="10">10代
  <option value="20">20代
  <option value="30">30代
  <option value="30">40代
  <option value="50">50代
  <option value="60">60代
  <option value="70">70代以上
</select>
</p>

<input type="submit" value="送信">

</form>

ブラウザで「http://localhost:4000/abc/form」にアクセスすると、以下のようなページが表示されます
image.png

「送信」ボタンを押下すると、入力されたパラメータが表示されますが、def.html.eexでやっているように、@ paramsの中身を使ってフォーム処理を行うことができます

checkboxは、選択したものが、リストで入ってきます
image.png

4)POSTフォーム処理も気軽に書く

POSTパラメータによるフォーム処理に対応するには、ルーティングにPOSTの分も書きます

lib/basic_web/router.ex
defmodule BasicWeb.Router do

  scope "/", BasicWeb do

    post "/*_path", PageController, :index  # <-- add here

formのmethodをPOSTに変更します

lib/basic_web/templates/page/abc/form.html.eex
<h1>I'm form page</h1>

<form method="POST" action="/abc/def">
              ^-- modify here
…

ブラウザで「http://localhost:4000/abc/form」にアクセスし、「送信」ボタンを押下すると、GET同様、POSTでも入力されたパラメータが表示されるようになります(GETのときはURLにパラメータがあったのに対し、POSTでは無くなっています)
image.png

終わり

Phoenixで、PHPみたいに気軽なWebアプリ開発をする手順を紹介しました

Phoenixは、ルーティングとMVCを前提にしていますが、Plugの機能とコントローラでのパラメータ処理を併用すれば、このような気軽さを導入することもできます

無論、ルーティングやMVCは、理由があって採用されている面もあるので、そのメリット/デメリットはちゃんと学んだり、理解しつつ、一方で、このテクニックを使って、簡単なWebアプリ構築もお楽しみください

p.s.このコラムが、面白かったり、役に立ったら…

image.pngimage.png にて、どうぞ応援よろしくお願いします:bow:

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

nginx で仮想ディレクトリにアプリをインストールしたときの REQUEST_URI の書き換え設定

やりたいこと

URL は http://example.com/foo/bar/action_name?id=1 なんだけど、 REQUEST_URI には /action/name?id=1 が入っててほしい場合の nginx の config の書き方。

設定例

/etc/nginx/conf.d/sample.conf
# アクセスされたURLの /foo/bar/ 以下の部分を、後で使うため変数 $request_uri_for_cgi, $query_string_for_cgi にセットする。
if ($request_uri ~* ^/foo/bar/(.*)$) {
    set $request_uri_for_cgi "/$1";
}
if ($request_uri ~* ^/foo/bar/(.*)\?(.*)$) {
    set $query_string_for_cgi "$2";
}

location ~* /foo/bar/(.*)$ {
    include /etc/nginx/fastcgi_params;

    fastcgi_pass   unix:/var/run/php-fpm.sock;
    fastcgi_index  index.php;
    fastcgi_split_path_info ^(.+?\.php)(/.*)$;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

    # REQUEST_URI と QUERY_STRING を設定する
    fastcgi_param REQUEST_URI $request_uri_for_cgi;
    fastcgi_param QUERY_STRING $query_string_for_cgi;

    try_files $uri $uri/ /foo/bar/index.php;
}

※nginx の設定は初めてなうえ、試行錯誤で作ったものなので、無駄な行があったりするかと思います。

結果

http://ecample.com/foo/bar/search?id=1234 にアクセスが有ると、 http://example.com/foo/bar/index.php が呼ばれて、 $_SERVER['REQUEST_URI']search?id=1234, $_SERVER['QUERY_STRING']id=1234 が入るようになります。

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

nginx で下層ディレクトリにアプリをインストールしたときの REQUEST_URI の書き換え設定

やりたいこと

URL は http://example.com/foo/bar/action_name?id=1 なんだけど、 REQUEST_URI には /action/name?id=1 が入っててほしい場合の nginx の config の書き方。

設定例

/etc/nginx/conf.d/sample.conf
# アクセスされたURLの /foo/bar/ 以下の部分を、後で使うため変数 $request_uri_for_cgi, $query_string_for_cgi にセットする。
if ($request_uri ~* ^/foo/bar/(.*)$) {
    set $request_uri_for_cgi "/$1";
}
if ($request_uri ~* ^/foo/bar/(.*)\?(.*)$) {
    set $query_string_for_cgi "$2";
}

location ~* /foo/bar/(.*)$ {
    include /etc/nginx/fastcgi_params;

    fastcgi_pass   unix:/var/run/php-fpm.sock;
    fastcgi_index  index.php;
    fastcgi_split_path_info ^(.+?\.php)(/.*)$;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

    # REQUEST_URI と QUERY_STRING を設定する
    fastcgi_param REQUEST_URI $request_uri_for_cgi;
    fastcgi_param QUERY_STRING $query_string_for_cgi;

    try_files $uri $uri/ /foo/bar/index.php;
}

※nginx の設定は初めてなうえ、試行錯誤で作ったものなので、無駄な行があったりするかと思います。

結果

http://ecample.com/foo/bar/search?id=1234 にアクセスが有ると、 http://example.com/foo/bar/index.php が呼ばれて、 $_SERVER['REQUEST_URI']search?id=1234, $_SERVER['QUERY_STRING']id=1234 が入るようになります。

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

【ポケモンで超ざっくり解説】オブジェクト指向

この記事の対象ユーザー

  • オブジェクト指向という言葉を知ったばかりの超初学者
  • スクールなどで学び始めた駆け出しエンジニア
  • オブジェクト指向を何となく理解している僕みたいなへっぽこエンジニア

詳しく知りたくて本記事にたどり着いた人はブラウザバック推奨。
あくまでも本記事はオブジェクト指向のイメージをざっくり理解することが目的なので
具体的なコードの書き方には言及いたしません。

この記事でざっくり理解できるもの

  • クラス
  • インスタンス
  • クラスの継承
  • メソッド
  • プロパティ

僕自身もようやく大枠を理解したばかりです。
修正箇所や説明の齟齬等ありましたらご指摘いただけると幸いです。

オブジェクト指向とは

簡単に説明すると、
いろんな機能を持ったプログラムを一つのまとまりにして、それを使えるようにしましょうというもの。
プログラムのまとまりのことをクラスといい、
それを使えるようにしたものをインスタンスという。
これらをオブジェクト指向といい、サーバースクリプトを扱う上で必須の概念と言われています。

自動車で例えるなら、

クラスが自動車の設計図で、
インスタンスが設計図を元に作った自動車とよく例えられています。
よくオブジェクトとインスタンスが混合しがちですが、厳密に異なります。
この場合だと車の部品ひとつひとつを生成する型をオブジェクトといいます。

正直、最初はこの説明でも僕の脳みそじゃ理解できませんでした(白目)

では、理解するのに苦労したオブジェクト指向を、一瞬で分かるようにみなさん大好きなポケモンで例えてみましょう。

ポケモンで例えるなら

今回登場していただくポケモンは僕の大好きなコイキングです。
1834616d5ae210cc15dde465ff60afb0.png

ポケモンずかん-コイキング

ポケモンクラス

trpg_character_sheet.png

ポケモンはたくさんいます。
コイキング用のクラス(設計図)だけ作ってしまうと、他のポケモンを作る時いちいち同じ設計図を作る必要があります。
面倒くさいですね。なのでポケモン全体に共通するものをまとめてポケモンクラスというものを作ります。

  • 分類
  • おもさ
  • タイプ
  • わざ
  • レベル

これらは全部のポケモンに与えられているものです。
これらのことをオブジェクト指向ではプロパティといわれます。
この状態では各項目は空です。枠だけ作るイメージですね。

また、ポケモン共通の動作である、

  • 鳴く
  • 攻撃する
  • 走る
  • レベルが上がる
  • ワザを出す

などポケモン共通のアクションがあります。
これらのことをオブジェクト指向ではメソッドといわれます。

このプロパティとメソッドを集めたものをクラスといいます。

コイキングクラス

ポケモンクラスを作ったことでたくさんのポケモンを作れるようになりました。
ただ僕が作りたいのは全ポケモン最強と(僕から)言われているコイキングです。
先程作ったポケモンクラスを使えば簡単に作れますね。

ポケモンクラスを継承しコダッククラスを作る

いきなり出てきた継承。
ざっくり親クラスのプロパティやメソッドを使い回せるということです。
ポケモンクラスが持つ空のプロパティを使い、コイキングの特徴を定義していきます。

ポケモンクラス コイキングクラス
タイプ みず
分類 さかなポケモン
おもさ 10kg
わざ はねる
鳴き声 ぎょえーー
レベル 99lv

これでコイキングプロパティは定義されたので、更にポケモンクラスから継承されたメソッドを利用してワザを出したり鳴けたりアクションを起こせるようにしてコイキングクラスを定義します。

ただここまではあくまで設計段階です。
これを実体化させなければただの妄想に過ぎません。

コイキングのインスタンスを生成

あとは簡単です。
ポケモンクラスを継承したコイキングクラスを実体化させてあげましょう。
オブジェクト指向ではこの実体化をインスタンスの生成と言われ、実体化したもの、つまり使えるようにしたものをインスタンスといいます。

これでやっとコイキングが使えるようになりました!!

まとめ

オブジェクト指向は初学者が理解に苦しむところです。
ただこれを早い段階で理解しているだけで後々読みやすいコードが書けたり、チーム開発が捗ったりと良いことずくめです。
今回はポケモンで例えてみましたが皆さんが好きなもので例えるのが一番理解しやすいと思います。

最後にオブジェクト指向のまとめ

  • オブジェクト指向:一つのプログラムでよく使うものをクラスとしてまとめ、インスタンス化させて実行させること。その概念。
  • クラス:メソッドやプロパティをまとめたもの
  • プロパティ:変数。数値や文字列などが定義されている
  • メソッド:関数。値を変更させたり、文字を追加させたり、動きに関するコード
  • インスタンス:クラスの中のメソッドやプロパティを使えるようにしたもの
  • 継承:親クラスのもつメソッドやプロパティを子クラスでも使えるようにする
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel セッションを使って一時的に値を保存しよう!

目的

  • Larvelでセッションを使う方法は理解したらとても便利だったため忘れない様にまとめる
  • DBは使用せずとにかくセッションを開く部分だけを紹介する

考え方

  • 「値に名前をつけて保存する」と「値が必要な時に名前を指定して取得する」の処理を利用するだ毛である。
  • 各クライアントのセッションIDという物と値の名前がそれぞれリンクして保存される。(これは値保存の概念として知っておいて欲しいが、セッションIDがクライアントに勝手に割り振られるため知らなくても実装できる)

書き方の例

  • 値に名前をつけて保存する方法を下記に記載する。

    session()->put('セッションに入れる値の名前', セッションに入れる値);
    
  • セッションに入れた値を取り出す方法を下記に記載する。

    取り出した値を格納する変数 = session()->get('セッションに入れた値の名前')
    

より具体的な例

  • 「Hello world」という文字列のセッションに入れてみる。
  • セッションに入れる際の値につける名前はstrとする。
  • セッションから値を受け取る時に格納する変数は$out_putとする。
  • 任意のコントローラに下記を記載する。

    session()->put('str', 'Hello world');
    
  • 別のコントローラに下記を記載する。

    $out_put = session()->get('str');
    
  • 上記の実行後、$out_putには文字列「Hello world」が入っている。

    • コントローラの変数をビューで表示する方法はこちら
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel リレーションの取得 withメソッド

withでeagerloadする

紐づいたテーブルから大量にデータ取得する場合、何度もDBアクセスしてデータを取得するとレスポンスがわるくなるので、リレーションを取得するときはwithを使う。

使い方

下記のようにするとwithメソッドが利用可能

Model::with('リレーション名')->get();

実際の例

1対多の関係(Personが複数のBoardを持っている)
親:person
 ・[id, name, email, age]
子:Board
 ・[id, person_id, title, message]
withを使うと下記のような流れで値を取得できる。
※hasMany, belongsTo等、正しく設定されていること前提
①Boardだけを取得
②取得したBoardのperson_idの値をまとめ、それらのIDのPersonを取得

Board.php
$boards = Board::with('person')->get();
return view('boards.index', ['boards' => $boards])
Boards/index.blade.php
# こうすると親の値も取得できる
@foreach($boards as $board)
  <p>{{ $board->message }}</p>
  <p>{{ $board->person->name }}</p>
@endforeach

上記は子から親を取得しているが、これの逆パターンをすれば親から子の取得も可能

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

Laravel7 消えたpassword_resetsテーブルを追え

Laravel7がリリースされまして初記事です。

事件発生

事件はLaravel7をインストールした時、起こりました。

$ composer create-project --prefer-dist laravel/laravel .
$ php artisan -V
Laravel Framework 7.2.2

$ ls -1 ./database/migrations
2014_10_12_000000_create_users_table.php
2019_08_19_000000_create_failed_jobs_table.php

password_resets ファアイルが消えた...!?

失踪したファイル

ファイルが削除されてしまっていた...!

転がり込んだファイル

laravel/ui ライブラリに移動されました。

検証

laravel/ui インストール

$ composer require laravel/ui

$ composer show laravel/ui
name     : laravel/ui
descrip. : Laravel UI utilities and presets.
keywords : laravel, ui
versions : * v2.0.1
type     : library
license  : MIT License (MIT) (OSI approved) https://spdx.org/licenses/MIT.html#licenseText
source   : [git] https://github.com/laravel/ui.git 47a0a1dac76f5e73803c86e1f38b2c7e0ae7fa83
dist     : [zip] https://api.github.com/repos/laravel/ui/zipball/47a0a1dac76f5e73803c86e1f38b2c7e0ae7fa83 47a0a1dac76f5e73803c86e1f38b2c7e0ae7fa83
path     : /work/vendor/laravel/ui
names    : laravel/ui
$ php artisan ui vue --auth

$ ls -1 ./database/migrations
2014_10_12_000000_create_users_table.php
2014_10_12_100000_create_password_resets_table.php
2019_08_19_000000_create_failed_jobs_table.php

--auth オプションを付けて実行した際に認証系のファイルが生成されますが、その際に 2014_10_12_100000_create_password_resets_table.php ファイルも生成されました。

謎はすべて解けた!

補足

ui vue で生成されるファイル一覧

$ php artisan ui vue
package.json
resources/js/app.js
resources/js/bootstrap.js
resources/js/components/ExampleComponent.vue
resources/sass/_variables.scss
resources/sass/app.scss
webpack.mix.js

ui vue --auth で生成されるファイル一覧

$ php artisan ui vue --auth
app/Http/Controllers/Auth/ConfirmPasswordController.php
app/Http/Controllers/Auth/ForgotPasswordController.php
app/Http/Controllers/Auth/LoginController.php
app/Http/Controllers/Auth/RegisterController.php
app/Http/Controllers/Auth/ResetPasswordController.php
app/Http/Controllers/Auth/VerificationController.php
app/Http/Controllers/HomeController.php
database/migrations/2014_10_12_100000_create_password_resets_table.php
resources/views/auth/login.blade.php
resources/views/auth/passwords/confirm.blade.php
resources/views/auth/passwords/email.blade.php
resources/views/auth/passwords/reset.blade.php
resources/views/auth/register.blade.php
resources/views/auth/verify.blade.php
resources/views/home.blade.php
resources/views/layouts/app.blade.php
routes/web.php
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel7 password_resetsテーブルはどこに消えた?

Laravel7がリリースされまして初記事です。

事件発生

事件はLaravel7をインストールした時、起こりました。

$ composer create-project --prefer-dist laravel/laravel .
$ php artisan -V
Laravel Framework 7.2.2

$ ls -1 ./database/migrations
2014_10_12_000000_create_users_table.php
2019_08_19_000000_create_failed_jobs_table.php

password_resets ファアイルが消えた...!?

失踪したファイル

ファイルが削除されてしまっていた...!

転がり込んだファイル

laravel/ui ライブラリに移動されました。

検証

laravel/ui インストール

$ composer require laravel/ui

$ composer show laravel/ui
name     : laravel/ui
descrip. : Laravel UI utilities and presets.
keywords : laravel, ui
versions : * v2.0.1
type     : library
license  : MIT License (MIT) (OSI approved) https://spdx.org/licenses/MIT.html#licenseText
source   : [git] https://github.com/laravel/ui.git 47a0a1dac76f5e73803c86e1f38b2c7e0ae7fa83
dist     : [zip] https://api.github.com/repos/laravel/ui/zipball/47a0a1dac76f5e73803c86e1f38b2c7e0ae7fa83 47a0a1dac76f5e73803c86e1f38b2c7e0ae7fa83
path     : /work/vendor/laravel/ui
names    : laravel/ui
$ php artisan ui vue --auth

$ ls -1 ./database/migrations
2014_10_12_000000_create_users_table.php
2014_10_12_100000_create_password_resets_table.php
2019_08_19_000000_create_failed_jobs_table.php

--auth オプションを付けて実行した際に認証系のファイルが生成されますが、その際に 2014_10_12_100000_create_password_resets_table.php ファイルも生成されました。

謎はすべて解けた!

補足

ui vue で生成されるファイル一覧

$ php artisan ui vue
package.json
resources/js/app.js
resources/js/bootstrap.js
resources/js/components/ExampleComponent.vue
resources/sass/_variables.scss
resources/sass/app.scss
webpack.mix.js

ui vue --auth で生成されるファイル一覧

$ php artisan ui vue --auth
app/Http/Controllers/Auth/ConfirmPasswordController.php
app/Http/Controllers/Auth/ForgotPasswordController.php
app/Http/Controllers/Auth/LoginController.php
app/Http/Controllers/Auth/RegisterController.php
app/Http/Controllers/Auth/ResetPasswordController.php
app/Http/Controllers/Auth/VerificationController.php
app/Http/Controllers/HomeController.php
database/migrations/2014_10_12_100000_create_password_resets_table.php
resources/views/auth/login.blade.php
resources/views/auth/passwords/confirm.blade.php
resources/views/auth/passwords/email.blade.php
resources/views/auth/passwords/reset.blade.php
resources/views/auth/register.blade.php
resources/views/auth/verify.blade.php
resources/views/home.blade.php
resources/views/layouts/app.blade.php
routes/web.php
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

XAMPPでXdebugが有効にならずERR_CONNECTION_RESETが出る

XAMPPでXdebugが有効にならず、ERR_CONNECTION_RESETが出た

概要

php5の開発時にはXdebugが有効だったが、php7だと有効にならずERR_CONNECTION_RESETが出た。
php5とは異なる設定を行うことで、php7でもXdebugを使うことができるようになった。
php5だとバージョンに合ったXdebugを探してダウンロードすれば良い。php7の場合、同様の手順では上手くいかないが、もともとXAMPPパッケージ内に準備されているDLLを指定すればOKだった。

開発環境

Windows: Windows 10 Home
Visual Studio Code: ver.1.43.1

php5のとき

XAMPP: ver.5.6.40 (php ver.5.6.40)
Xdebug: php_xdebug-2.5.5-5.6-vc11-x86_64.dll

php7のとき

XAMPP: ver.7.3.15 (php ver.7.3.15)
Xdebug: php_xdebug.dll(パッケージ内にあるもの)

Xdebugを動かすための前準備

  • XAMPPのインストール
  • ApacheのListenポートを設定
  • ApacheのDocumentRootを開発リポジトリに合わせて設定
  • Apacheのhttpd-xampp.confを修正し、リポジトリのDirectoryへの操作権限を追加
  • Visual Studio CodeにPHP Debugを追加
  • Visual Studio Codeでlaunch.jsonを設定

php5のとき

まずphpinfo()をコードに書いてXAMPPのApache上で実行し、PHP情報を表示する。下記の例ではApacheのDocumentRootを~\public\life-planning\に設定してある。その直下にあるtop.phpにphpinfho()を書く。
キャプチャ2.PNG
例としてApacheのListenポートを9480にしている場合なら、上記top.phpを開くには、ブラウザのURLをlocalhost:9480/top.phpにする。
PHP情報が表示されたら、全体の文字を選択して、コピーする。
無題.png

Xdebugインストールウイザード を開いてテキストボックスに貼り付け、Analyse my phpinfo() outputをクリックすると、使うべきXdebugバージョンを教えてくれる。

そのバージョンをダウンロードし、xampp\php\extフォルダに貼り付ける。php.iniにzend_extentionを追記する。例として以下の場合XAMPPのインストール先はD:\Pgm\xampp\となっている。

php.ini
zend_extension = D:\Pgm\xampp\php\ext\php_xdebug-2.5.5-5.6-vc11-x86_64.dll
[xdebug]
xdebug.default_enable = 1
xdebug.idekey = "vscode"
xdebug.remote_enable = 1
xdebug.remote_port=9000
xdebug.remote_autostart=1

Visual Studio Code上にブレークポイントを設定しF5を押してデバッグ実行を開始する。ブラウザからwebページを開くと、ブレークポイント上で実行が中断されてデバッグ実行ができるようになる。
キャプチャ4.PNG

php7のとき(うまく動かなかったやり方)

php5のときと同様に Xdebugインストールウイザード でxdebugバージョンを解析すると、php_xdebug-2.9.3-7.3-vc15-x86_64.dllと出た。同ファイルをダウンロードしてextフォルダに貼り付けた。php.iniのzend_extentionを指定した。

php.ini
zend_extension = D:\Pgm\xampp\php\ext\php_xdebug-2.9.3-7.3-vc15-x86_64.dll.dll
[xdebug]
xdebug.default_enable = 1
xdebug.idekey = "vscode"
xdebug.remote_enable = 1
xdebug.remote_port=9000
xdebug.remote_autostart=1

ところが、Apacheが沈黙してしまう。このサイトにアクセスできません、ERR_CONNECTION_RESETのエラーが出ている。
キャプチャ6.PNG
いろいろ試してみたところ、XdebugのDLLファイル名を存在しない名称に書き替えたらXdebug無しのまま応答してくれる。なので、設定エラーではなく、DLL側に組み合わせの問題があるのかなという気がする。しかし Xdebugインストールウイザード で選ばれた組み合わせに問題があるとすれば、どうしてよいやら。

XAMPP全体をアンインストールして再インストールしたが、同じ問題が起こり解決しなかった。ブラウザのキャッシュをクリアしたり、OS再起動したりしても解決しない。

php7のとき(解決したやり方)

extフォルダ内に、バージョン名がない無印の名称のphp_xdebug.dllというファイルが存在する。php5のときもあったが、気にしていなかった。上記問題の対応策を探っていたところ、この無印バージョンを指定したら、Xdebugが使えるようになった。

php.ini
zend_extension = D:\Pgm\xampp\php\ext\php_xdebug.dll
[xdebug]
xdebug.default_enable = 1
xdebug.idekey = "vscode"
xdebug.remote_enable = 1
xdebug.remote_port=9000
xdebug.remote_autostart=1

Xdebugが有効になったことを確認するためには、Visual Studio Codeのブレークポイントを使っても良いし、phpinfo()を表示して、下の方にxdebugの項目が表示されていることを確認しても良い。

キャプチャ8.PNG

課題

Xdebugが有効になったので問題は解決だが、無印の名称のDLLだと他の問題がないのかどうか未調査。また、なぜ Xdebugインストールウイザード で指定されたバージョンが有効にならなかったのかは、いまだ不明。

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

HTMLページをHerokuにデプロイ

この記事について

(お金をかけずに)SPAやアプリをインターネットに公開したいのにgithub pagesはもう使用済みでどうしよう、と思っていたところ、Herokuをおすすめしてもらい、実際に無料で公開できたので、手順を記録しておきたく記事にしました。

今回はHTML、CSS、JSファイルで作ったSPA(Single Page Application)をHerokuにデプロイしました。とりあえずこの記事ではデプロイするところまでをまとめています。

デプロイ後のログの確認や、コードの追加修正を反映させる操作などについては、別途まとめたいと思っています。

1. まず、HTMLで作ったSPAをアプリにする

HTMLやCSS、JSファイルだけで構成されたディレクトリはherokuではアプリとして認識されないため、HTMLファイルがあるディレクトリと同じディレクトリにindex.phppackage.jsonを追加して、phpアプリに変身させます。

index.php
<?php include_once("index.html"); ?>
package.json
{}

HTMLとJavaScriptで作ったサイトがHerokuにデプロイできねええって時の対処法
↑の記事を参考にさせていただきました。

2. Herokuアカウントを作る

Herokuでアカウントを作成します。

3. デプロイしていく

Getting Started on Heroku with PHPに沿ってデプロイをしていきます。

3-1. まず必要な準備

上記リンクに飛び、「Get started with php」をクリックすると、↓の画面に遷移します。
スクリーンショット 2020-03-21 19.24.24.png

始める前に、必要なもの準備できてますか?!という確認です。
下記の3つの準備が整っていることを求められています。

  • Herokuアカウントがあること
  • PHPがローカルにインストールされていること
  • Composerがローカルにインストールされていること

phpはMacにはデフォルトで入っているのでインストール不要でした。
ComposerはHomebrewからインストールします。
$ brew install composer

3-2. herokuをインストール

必要な準備が終わったら「I'm ready to start」をクリックして次に進みます。
次はherokuをインストールします。
スクリーンショット 2020-03-21 19.25.03.png
インストールコマンドが載っているのでターミナルで実行。
$ brew install heroku/brew/heroku

3-3. herokuにログイン

herokuのインストールが完了したらログインしてみます。

$ heroku login
と打つと、
heroku: Press any key to open up the browser to login or q to exit:
と返されるので、Enterします。

すると↓のページがブラウザに出るのでログインします。
スクリーンショット 2020-03-21 19.39.38.png

ログインした後に遷移するページは、そのページにも書いてありますが閉じてしまっても大丈夫なようです。

3-4. ツールのバージョン確認

php、composer、gitのバージョンを確認します。

バージョンの確認というか、インストールできてるかの確認です。下記それぞれのコマンドを実行してバージョンが表示されればインストールできているとわかります。

$ php -v
$ composer -v
$ git --version

3-5. herokuにデプロイするリポジトリをcloneする

githubからherokuにデプロイしたいリポジトリをローカルにcloneします。

$ git clone <url>

このリポジトリにはHTMLとCSS,JSで作られたSPAに、「1. まず、HTMLで作ったSPAをアプリにする」の手順で追加したphpファイルとjsonファイルが含まれています。

3-6. アプリをherokuにデプロイ

ローカルにクローンしたリポジトリに移動し、下記のコマンドを順番に実行していきます。

(1) $ heroku create

Creating app... done,

の後に続くワードが今回作成されたアプリ名になります。

(2)$ git push heroku master
(3)$ heroku ps:scale web=1
(4)$ heroku open

これでデプロイ完了です! HTMLで作ったSPAが新しいタブに表示されるかと思います。
もし表示されなくても、Herokuのダッシュボードを見るとアプリ名が表示されているので、そこへ飛び、右上の「Open」をクリックするとSPAに遷移できます。

終わりに

作ったものをデプロイしてURLを渡すだけで色々な人に見てもらえるようになるのは嬉しいですね。

プログラミングの勉強をし始めた時にも感じましたが、努力が何かしら形になるとモチベーション上がります!!SPA作ったとか、フレームワークのインストールができたとか、そういうことでも嬉しかったです。

デプロイ作業は今回初めてで、まだまだわからない事ばかりで手探り状態ですが、とりあえずできたので、しっかり運用できるようにしていきたいと思います。

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

PHP で Go の defer っぽいものを実装する

はじめに

Go には defer という return などの前に共通で呼び出してくれる機能があります。

package main

import (
    "fmt"
)

func main() {
    defer func() {
        fmt.Printf("Hello World 1\n")
    }()
    fmt.Printf("Hello World 2\n")
}

上記の結果は

Hello World 2
Hello World 1

です。この記事は、これを PHP でやる手段はあるかどうかについて書きます。

Q. PHP で defer はできるのか?

A. できます。

try {
    echo "Hello World2\n";
} finally {
    echo "Hello World1\n";
}

または

class A 
{
    public function __destruct()
    {
        echo "Hello World!\n";
    }
}

$_ = new A();
echo "Hello World1\n";

上記のようにすれば PHP でも defer を実現することができます。

ちょっと待てよ?

すごく分かりづらくないですか?特に finally の場合だと複雑に defer の登録がされた場合、とても面倒くさいです。
Go の場合、

package main

import (
    "fmt"
)

func main() {
    defer func() {
        fmt.Printf("Hello World 1\n")
    }()

    if err == nil {
        return
    }

    defer func() {
        fmt.Printf("Hello World 3\n")
    }()
    fmt.Printf("Hello World 2\n")
}

のように条件文の後に defer を書くこともできます。 PHP の場合だと、実行用の変数に一度格納してから〜… つまり

try {
    $deferredFunctions = [];

    $deferredFunctions[] = function () {
      echo "Hello World1\n";
    };
    echo "Hello World2\n";
} finally {
    foreach ($deferredFunctions as $deferredFunction) {
      $deferredFunction();
    }
}

上記のように書かないといけないわけです。実現できるっちゃできますが、非常に面倒くさいですね。 defer な処理をしたいものを増やすたびに上記のようなコードを無限大に書かなくてはいけないわけです。
defer の処理をするために、インデントを深くする必要があるのが辛い。そう、私達が求めているのは PHP でも Go の defer っぽく書けることです

defer(function () {
  echo "Hello World1\n";
});

echo "Hello World2\n";

こうしたいわけですよね。

既に php-defer/php-defer がある

なるほど...

defer($_, function () {
  echo "goodbye\n";
});

$_ は書かなきゃいけないのか…なんとかならないのか?

ということで、自作をしました。

php-deferrable

上記のライブラリは $_ を書かなくても済みます。

use function PHPDeferrable\defer;
use function PHPDeferrable\deferrable;

class MyClass
{
    public function doSomething1()
    {
        defer(function () {
            echo "Three!\n";
        });

        defer(function () {
            echo "Two!\n";
        });
        echo "One!\n";
    }

    public function doSomething2()
    {
        defer(function () {
            echo "NyanNyan!\n";
        });
        echo "Wanwan!\n";
    }
}

/**
 * @var MyClass $myClass
 */
$myClass = deferrable(MyClass::class, ...$somethingArguments);
$myClass->doSomething1();
$myClass->doSomething2();

のように直感的に書けます。そして、クラスだけではなく callable なものに対しても defer を登録できるようにしています。

use function PHPDeferrable\defer;
use function PHPDeferrable\deferrable;

deferrable(function () {
    defer(function () {
        echo "0: deferred call\n";
    });
    echo "0: first call\n";
})();

上記は下記のように出力されます。

0: first call
0: deferred call

これで PHP でも defer な処理ができますね!やったね?

どう実装しているのか?

詳しく知りたい方はコードを読んでみてほしいです。
クラスの場合における触りだけ解説をします。

deferrable(MyClass::class)

とすることにより deferrable 関数から makeContextManipulator に渡されます。ここでは既存のコンテキストに対して defer が使えるようになるようにコードに多少小細工をしています。

ReflectionClass を使って対象のクラスに登録されているメソッドを取り出して、各メソッドに try-finally とコンテキスト内で登録された defer を走らせる仕組みになっています。

foreach ($reflection->getMethods() as $method) {
    $methodName = $method->getName();
    if ($method->isAbstract()) {
        continue;
    }
    $body[] = static::makeMethodSignature($method) . ' { '
        . '$deferContext = \\' . __NAMESPACE__ . '\\Deferrable::createDeferContext(' . $scope->getScopeType() . '); '
        . 'try{'
        . '$result = parent::' . $methodName . '(...func_get_args()); '
        . '} finally {'
        . '\\' . __NAMESPACE__ . '\\Deferrable::consume($deferContext);'
        . '}'
        . 'return $result; '
        . '}';
}

abstract の場合はそもそも呼べないものなので、スルーをしています。そしてこれらの値を元々のクラスを継承した新しいクラスにします。

$temporaryClassName = Runtime::DEFER_ANONYMOUS_CLASS_PREFIX . (static::$temporaryClassCounter++);

eval(
    'class ' . $temporaryClassName . ' extends ' . $scope->getClassName() . ' implements \\' . __NAMESPACE__ . '\\Contracts\\DeferrableInterface'
    . '{'
    . implode($body)
    . '}'
);

こうすると、下記のようなコードが生成されます。

class __defer__anonymous_0 extends MyClass implements \PHPDeferrable\Contracts\DeferrableInterface
{
    public function doSomething1()
    {
        $deferContext = \PHPDeferrable\Deferrable::createDeferContext(1);
        try {
            $result = parent::doSomething1( ...func_get_args());
        } finally {
            \PHPDeferrable\Deferrable::consume($deferContext);
        }
        return $result;
    }
    public function doSomething2()
    {
        $deferContext = \PHPDeferrable\Deferrable::createDeferContext(1);
        try {
            $result = parent::doSomething2( ...func_get_args());
        } finally {
            \PHPDeferrable\Deferrable::consume($deferContext);
        }
        return $result;
    }
}

このコードで defer を実現している、ということになります。

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

暗号化って結局どれを選べば良いの?OWASP Cryptographic Storage Cheat Sheetまとめ

はじめに

WEBアプリケーションを開発するときに、「個人情報は暗号化して保存すること」といった要件を見かけます。一口に暗号化といっても、共通鍵暗号、公開鍵暗号、AES、DES、RSA、SHA、暗号モード、ブロック暗号と色々なワードが出てきて、どれを選んだら良いかわからなくなります(した)。本記事では、その時に調べてわかったことを備忘録的に書きます。

本記事では、以下に2つを参考にしています。

なお、パスワードは、本記事で紹介する暗号化して保存することはしないでください。パスワードはハッシュ化して保存します。詳しくは、OWASP ASVS 4.0 V2.4 からパスワードの保存を考えるという記事で紹介してます。
OWASP Password Storage Cheat Sheetの方がもちろん詳しい。

暗号化の前に

OWASP Cryptographic Storage Cheat Sheetにも書かれてますが、もう一度「そのデータいる?」を検討しましょう。暗号化する以前に、保存しないのが一番です。
例えば、クレジットカード情報は多くのECサイトで決済時に入力しますが、ECサイトはクレジットカード番号を保存しません。保存しなくても良い仕組みができています。

可能な限り、暗号化しなければならないようなデータの保管は、避けてください。

データをどのレベルで暗号化するか?

まず、考えるべきは、どのレベルで暗号化するか?

  • アプリケーションレベル
  • データベースレベル
  • ファイルシステムレベル(BitLockerなど)
  • ハードウェアレベル(HDDやSSDの暗号化)

例えば、ハードウェアレベルで暗号化した場合、HDDを持ち出すような物理的な攻撃には強いですが、リモートでアクセスした場合には、意味をなさないです。また、アプリケーションレベルで暗号化してデータベースに保存する場合、SQLレベルで検索することはできないでしょう。常にアプリケーションからそのデータを閲覧する以外に方法はありません。また、その場合も内部犯行による持ち出しの場合には、意味をなさないかもしれません。

暗号アルゴリズム

最適な暗号アルゴリズムは、常に変わります。CRYPTREC暗号リストの変更履歴を見ると、年に1回見直されてます。今日安全な暗号アルゴリズムも、明日には攻撃方法が発見されているかもしれません。なので、変更できるようにしておくことが重要です。

共通鍵暗号(Symmetric encryption)

暗号化と復号に同一の(共通の)鍵を用いる暗号方式です。暗号化と聞いて、最も思いつきやすい方式だと思います。

FireShot Capture 001 - 無題のプレゼンテーション - Google スライド - docs.google.com.png

後述する公開鍵暗号に比べて、高速で暗号化と復号ができるいうメリットがあるものの、鍵が共通なので、安全な鍵の受け渡しが必要です。鍵さえわかってしまえば復号できるわけなので。また、比較的大きなデータでも暗号化を行うことができます。
例えば、ローカルに個人情報を暗号化して保存するときなどの鍵を受け渡しが必要ない時に使える暗号化方式です。

では、共通鍵暗号の場合、どの暗号アルゴリズムを使えば良いか。

OWASP Cryptographic Storage Cheat Sheetでは、

AES with a key that's at least 128 bits (ideally 256 bits) and a secure mode

CRYPTREC暗号リスト(電子政府推奨暗号リスト)では、

128ビットブロック暗号: AES、Camellia
ストリーム暗号: KCipher-2

なので、2つに記述のあるAES-128もしくはAES-256が良いと思われます。AESが何かなどの細かい説明は端折ります。wikipediaとか見てもらえればと思います。

secure mode(暗号利用モード)

AESは、ブロック暗号と呼ばれる暗号アルゴリズムですが、直接使用せず、暗号利用モードを使います。暗号利用モードとは、AESなどのブロック暗号を利用して、任意の長さの平文を暗号化できるようにするメカニズムです。

で、この暗号利用モードにもいくつか選択肢があります。

OWASP Cryptographic Storage Cheat Sheetでは、

Where available, authenticated modes should always be used. The most commonly used authenticated modes are GCM and CCM.
If GCM or CCM are not available, then CTR mode or CBC mode should be used.

CRYPTREC暗号リスト(電子政府推奨暗号リスト)では、

秘匿モード : CBC,CFB,CTR,OFB
認証付き秘匿モード : CCM,GCM

可能ならば、CCMかGCMも使いましょう。

認証付き秘匿モード(CCM,GCM)

認証付き秘匿モードとは、復号する時に元のデータから改竄されていないかを同時にチェックするモードです。

encrypt.php
$key = 'hogehoge';
$cipher = "aes-256-gcm";
$iv_len = openssl_cipher_iv_length($cipher);
$iv = random_bytes($iv_len); // 説明の都合で同じ初期ベクトルを使う

// a → aa
$a = base64_encode(random_bytes(100));
$aa = openssl_encrypt($a, $cipher, $key, 0, $iv, $tag_a);

// b → bb
$b = base64_encode(random_bytes(100));
$ivb = random_bytes($iv_len);
$bb = openssl_encrypt($b, $cipher, $key, 0, $iv, $tag_b);

// bbを戻す時にaの認証タグを使うとfalseになる
$result = openssl_decrypt($bb, $cipher, $key, 0, $iv, $tag_a); // false

暗号化した時に同時できる認証タグ($tag_a$tag_b)と暗号文のセットが異なっていれば、復号できなくなります。暗号文が、改竄された場合、復号できなくなるので、機密性と同時に完全性も担保できる優れものです。

暗号アルゴリズムと鍵の変更

暗号化を実装する時には、先にも書いたように暗号アルゴリズムが変わることを前提に設計する必要があります。また、鍵についても変わることを前提にした設計にする必要があります。変わった場合にどうするか?

  • 全ての暗号文を復号して、新しい暗号アルゴリズムと鍵で暗号化し直す。
  • 暗号化の際に、アルゴリズムと鍵のIDを保管しておき、古い暗号アルゴリズムと鍵でも復号できるようにしておき、保管の際に新しい暗号アルゴリズムと鍵を適用する

前者がベストです。ただ、全てを置き換えることは、手間がかかるかもしれません。
そのため、後者のように、暗号文を保存する時に、暗号アルゴリズムのIDと鍵のIDを一緒に保存しておくと良いでしょう。{1,1}....みたいな感じです。そうすることで、一気に変更する手間がなくなります。ivtagと合わせて、json形式などで保存しておくと良いかもしれません。

encrypt.json
{
    "key": 1,
    "cipher": 1,
    "txt": "暗号文",
    "iv": "$iv",
    "tag": "$tag"
}

暗号学的擬似乱数生成器(CSPRNG)

暗号化とは、ちょっと話がそれますが、暗号化鍵、初期化ベクトル(ストリーム暗号やブロック暗号の任意の暗号利用モードで暗号化する時に、毎回同じ平文から同じ暗号文を生成しないように指定する)、セッションID、CSRFトークン、パスワードリセットトークンなど、ランダムな文字列を生成する時に、乱数を生成します。これらの乱数は、ランダム性が重要となり、推測されてはなりません。そういった時に、暗号学的擬似乱数生成器(CSPRNG)を使います。

PHP7なら、random_bytes(), random_int()です。PHP5なら、openssl_random_pseudo_bytes()です。

なお、擬似乱数生成器(PRNG)は、高速でセキュリティ関連機能以外で利用できます。例えば、ランダムにページの表示順を変えるなどの機能です。

PHPならrand(),mt_rand(), array_rand(), uniqid()です。

その他の言語は、OWASP Cryptographic Storage Cheat Sheetを参考にしてください。

公開鍵暗号(Asymmetric encryption)

暗号化と復号に別の鍵(公開鍵と秘密鍵)を用い、一方の鍵(公開鍵)を公開できるようにした暗号方式です。共通鍵暗号は、共通鍵が漏洩してしまうと、簡単に復号されます。共通鍵の交換に非常にコストがかかりましたが、この問題を解決したのが公開鍵暗号です。

FireShot Capture 003 - 無題のプレゼンテーション - Google スライド - docs.google.com.png

公開鍵で暗号化した暗号文は、対となる秘密鍵でしか復号できません。また、公開鍵から秘密鍵を得ることもできません。なので公開鍵をどれだけ公開しても、暗号が破られることはありません。

実際にやってみたいと思いますが、公開鍵暗号は、どういった暗号アルゴリズムを使ったら良いでしょうか?

OWASP Cryptographic Storage Cheat Sheetでは、

elliptical curve cryptography (ECC) with a secure curve such as Curve25519. If ECC is not available and RSA must be used, then ensure that the key is at least 2048 bits.

CRYPTREC暗号リスト(電子政府推奨暗号リスト)では、

署名:DSA、ECDSA、RSA-PSS、RSASSA-PKCS1-v1_5
守秘:RSA-OAEP

ここでは、RSA-OAEPでやってみましょう。

// 鍵ペアを作成する
$ openssl genrsa 2048 > ./private_key.pem  
$ openssl rsa -pubout < private_key.pem > pub_key.pem
rsa.php
// 公開鍵で暗号化
$data =random_bytes(100);
$public_key = file_get_contents(__DIR__ . "/pub_key.pem");
$result = openssl_public_encrypt($data, $crypted, $public_key, OPENSSL_PKCS1_OAEP_PADDING);

$cryptedに暗号文が入ります。$resultに結果がboolで入ります。

なお、公開鍵暗号は、共通鍵暗号に比べて暗号化と復号に時間かかるというデメリットがあリます。さらに鍵長とpaddingの方式によって一度に暗号化できるサイズが決まってます。

鍵長:2048bitの場合は下記でした。

padding 平文の長さ
OPENSSL_PKCS1_PADDING 245 byte
OPENSSL_PKCS1_OAEP_PADDING 215 byte

もちろん、長い平文を分割して、暗号化することも可能ですが、通常はめんどくさいんで、共通鍵暗号で暗号化します。
SSL/TLS通信でも、実際の暗号化は共通鍵暗号を使われますが、その共通鍵の交換に公開鍵暗号で鍵交換が行われています。

鍵の管理

共通鍵暗号、公開鍵暗号いずれの暗号化方式でも鍵を適切に管理する必要があります。

鍵の生成

鍵の生成に、暗号学的擬似乱数生成器(CSPRNG)のように推測されにくいランダムな文字で生成するようにします。共通のワードやフレーズを使うなどはやめましょう。パスワードとは違い、鍵を手入力する必要はないので、本当にランダムなもので良いはずです。

鍵のライフタイムとローテーション

鍵は様々なタイミングで変更する必要があります。

  • 鍵が漏洩した時
  • 鍵を知っている人が退職した時
  • 予め定めた期間を経過した時
  • 一定量暗号化した時(64ビット鍵の場合は34GB、128ビット鍵の場合は295エクサバイト)
  • アルゴリズムに攻撃方法が見つかった時

頻繁に変更することはありませんが、1年以上運用するようなシステムであれば必ず1回ぐらいは変更する必要があるかもしれません。予め設計に組み込んでおく必要があります。変更方法は、「暗号アルゴリズムと鍵の変更」のセクションで記載しました。

鍵の保管

鍵の保管は、難しいですね。アプリケーションが、データを常に復号するためには、アプリケーションが鍵にアクセスできる必要がありますが、アプリケーションが侵害された場合は、鍵も盗まれる可能性があります。設定ファイルなどに記載していると盗まれるでしょう。下記に記載する方法は、完全に防御できるわけではないですが、盗まれるのを難しくします。

  • 物理HSM
  • 仮想HSM
  • Amazon KMS や Azure Key Vault

上記のような鍵管理でできない場合でも、下記のことは必ず守るようにしましょう。

  • 鍵をアプリケーションの中にハードコーディングしない(設定ファイルに書く)
  • 設定ファイルには適切な閲覧権限を与える
  • git(などのバージョン管理)に鍵をコミットしない
  • 環境変数に鍵を保存しない。誤って公開されてします危険性があります。

特にやりがちなのが、git(などのバージョン管理)に鍵をコミットすることです。コミット履歴にも残るので結構厄介です。

また、鍵はデータとは別に保管する必要があります。例えば、データベースにデータを保管する場合は、鍵はファイルシステムに保管する必要があります。そうすることで、SQLインジェクションでデータを取られても直ちに解読される心配はないでしょう。

鍵を暗号化する鍵

データ暗号化するための鍵を暗号化して保管する方法もあります。この場合、少なくとも2つの個別の鍵を管理する必要があります。

  • データを暗号化するための鍵(DEK)
  • DEKを暗号化するための鍵(KEK)

DEKとKEKの鍵の管理方法の詳細は、Googlのエンベロープ暗号化というドキュメントに記載されています。
ちなみに、KEKとDEKを別々に保存できない場合は、このように複雑にすることはかえってデメリットになるかもしれませんが、攻撃者が復号するためのハードルをあげることにはなります。

暗号化手順
1. DEKを生成します。暗号化のたびに新しいDEKを作成します。
2. DEKでデータを暗号化します。
3. 一元管理されたKEKでDEKをラップ(暗号化)します
4. 暗号化データとラップされたDEKを一緒に保存します

復号手順
1. 暗号化データとラップされたDEKを取得します
2. KEKからDEKのラップを解除します
3. 平文のDEKでデータを復号します

ちなみに、Amazon KMSもこのエンベロープ暗号を使われています。

Amazon KMSを使った実装

Amazon KMSで鍵を管理し、暗号化を実装してみます。暗号化には、共通鍵暗号 AESの256ビットGCMモードを使うことにします。

  1. AWSマネジメントコンソール上でCMK(カスタマーマスターキー)を作成します。
  2. AWS SDK for PHPをcomposerでインストールします
  3. 暗号化処理を実装します
  4. 復号処理を実装します

1. AWSマネジメントコンソール上でCMK(カスタマーマスターキー)を作成します。

CMKとは、上記でいうKEKです。

[キーの作成]をクリックします
FireShot Capture 008 - KMS Console - ap-northeast-1.console.aws.amazon.com.png

[エイリアス]に名前を入力して次へ
FireShot Capture 010 - KMS Console - ap-northeast-1.console.aws.amazon.com.png

管理者を選択します。うっかり削除すると復号ができなくなるので、最小限にしましょう。
FireShot Capture 012 - KMS Console - ap-northeast-1.console.aws.amazon.com.png

利用できるアカウントやロールを選択します。AWS SDK for PHPを使うアカウントかロールを選択します。
FireShot Capture 013 - KMS Console - ap-northeast-1.console.aws.amazon.com.png

完了をクリックして、完了です。
FireShot Capture 014 - KMS Console - ap-northeast-1.console.aws.amazon.com.png

できてますね。
FireShot Capture 015 - KMS Console - ap-northeast-1.console.aws.amazon.com.png

クリックして開いた画面のARNをあとで使います。
FireShot Capture 016 - KMS Console - ap-northeast-1.console.aws.amazon.com.png

2. AWS SDK for PHPをcomposerでインストールします

composer require aws/aws-sdk-php

詳細は、下記を参照してください
https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/getting-started_installation.html

3. 暗号化処理を実装します

public function encrypt($message)
{       
    $KmsClient = new Aws\Kms\KmsClient([
        'profile' => 'default',
        'version' => '2014-11-01', // このバージョンを指定するようです
        'region'  => 'ap-northeast-1' // リージョンを指定します
    ]);

    // Generate a data key
    // CMK ID または ARN に指定します。
    $keyId = '';
    $keySpec = 'AES_256';

    $result = $KmsClient->generateDataKey([
        'KeyId' => $keyId,
        'KeySpec' => $keySpec,
    ]);

    $plaintextKey = $result['Plaintext']; // 平文キー
    $encryptedKey = $result['CiphertextBlob']; // 暗号化されたキー

    $cipher = $config["cipher"]["current"]; //aes-256-gcmが入っているものとしてください
    $iv_len = openssl_cipher_iv_length($cipher);
    $iv = random_bytes($iv_len);
    $crypted = openssl_encrypt($message, $cipher, $plaintextKey, 0, $iv, $tag);

    $result = [
        "iv" => bin2hex($iv),
        "cipher_id" => $config["cipher"]["current"]["id"],
        "text" => bin2hex($crypted),
        "key" => bin2hex($encryptedKey),
        "tag" => bin2hex($tag),
    ];
    return json_encode($result); // 暗号文と一緒にiv,key,tagをjson形式で保管

}

詳細は、AWS KMS API のプログラミングを参照してください

4. 復号処理を実装します

public function decrypt($crypted)
{
    [ 
        "iv" => $iv,
        "cipher_id" => $cipher_id,
        "text" => $text,
        "key" => $key,
        "tag" => $tag
    ] = json_decode($crypted, true);

    $cipher = $config["cipher"][$cipher_id]; //aes-256-gcmが入っているものとしてください
    $KmsClient = new Aws\Kms\KmsClient([
        'profile' => 'default',
        'version' => '2014-11-01',
        'region'  => 'ap-northeast-1'
    ]);

    $result = $KmsClient->decrypt([
        'CiphertextBlob' => hex2bin($key) // キーを復号
    ]);

    $plaintextKey = $result['Plaintext']; // 平文キー
    $message = openssl_decrypt(hex2bin($text), $cipher, $plaintextKey, 0, hex2bin($iv), hex2bin($tag));

    return $message; // 改竄されているとfalseになる
}

まとめ

暗号化の要件があった場合には、自作するのではなくクラウドやミドルウェアが提供する暗号化機能を、まず検討しましょう。安全な暗号化アルゴリズムや鍵を使い続けるためには、労力が必要です。
それでもアプリケーションのレベルで暗号化が必要な場合は、本記事を参考にしてもらえたら幸いです。

参考

https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html
https://www.cryptrec.go.jp/method.html
https://cloud.google.com/kms/docs/envelope-encryption
https://aws.amazon.com/jp/kms/
https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/programming-top.html

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