20200517のPHPに関する記事は21件です。

【Laravel・Eloquent】modelの関数名になぜscopeをつけるのか

scopeの定義方法

class Hoge extends Model {
  public function scopeFuge($query, $code) {
    //処理
  } 
}

関数名にscopeを追加し、引数$query$code(引数名は自由)を定義します。

引数

$queryとは

この引数は、whereで取得されるのと同じBuilderインスタンスが渡されます。

$code(引数名は自由)

コントローラから渡された引数がこの値に代入されます。

Person.php(Model)

class Person extends Model {

    public function scopeNameEqual($query, $str) 
        {
            return $query->where('name', $str);
        }
}

PersonController.php(Controller)

use App\Person;

class PersonController extends Controller {

    public function search(Request $request) {
        $item = Person::nameEqual($request->input)->first();
        //〜その他処理は割愛〜
        return view('person.find, $param);
    }
}

scopeを呼び出す時は、関数名のscopeを省略します。

なぜscopeを使うのか

scopeを使うことにより、上記の2つの引数が利用できるようになります。

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

is_xxx() 関数を使わずに型判定できますか?

問題

\CarrotRakko という名前空間の直下に以下の8つの関数を定義せよ。定義する関数のシグネチャと外から見た挙動は、グローバル名前空間に存在する同名の関数と同じにすること。

関数/メソッド呼び出しは行なってはならない。ここで、関数/メソッドの呼び出しとはPHP処理系の実装レベルで関数/メソッドがコールされることを指すのであって、PHPコードでの呼び出し方は問わない。なお、以下のソースコードでは関数/メソッド呼び出しを行わずに実装できる部分を、関数/メソッド呼び出しによって簡潔に表現することがある。

実装に用いる言語およびバージョンは PHP 7.4.x とする。公式でない処理系や標準でバンドルされていないエクステンション、自作のエクステンションの使用などは認めない。

問1: is_bool(mixed $var): bool
問2: is_int(mixed $var): bool
問3: is_float(mixed $var): bool
問4: is_string(mixed $var): bool
問5: is_array(mixed $var): bool
問6: is_object(mixed $var): bool
問7: is_resource(mixed $var): bool
問8: is_null(mixed $var): bool

問1: is_bool(mixed $var): bool

最初に思いついたのは次のような実装です。

<?php

namespace CarrotRakko;

/**
 * boolean型かどうか
 *
 * @param mixed $var
 * @return bool
 */
function is_bool($var): bool
{
    return ($var === true) || ($var === false);
}

boolean型 には truefalse の2値しかないのでこういうことができます。しかしながら、型の中には取りうる値が有限個ではないもの( string型, array型, object型, resource型 )や、有限個ではあるがリテラルをベタがきするには多すぎるもの( int型, float型 )があるため、boolean型 の値の種類の少なさに依存しない実装をしてみたいところです。

function is_bool($var): bool
{
    return !!$var === $var;
}

ここで 論理否定演算子Logical Not Operator) について確認しておきたいと思います。 !$var が評価されるときはまず、 $varboolean型に変換 されます。変換された結果が true であれば !$varfalse に、 false であれば !$vartrue に評価されます。

論理否定演算子の確認が済んだところで、 booleanへの変換Converting to boolean)を理解しましょう。リンク先を読むと、PHPの8つの型はいずれもboolean型に変換できることがわかります。

ここで、 ! を使って実装した is_bool(mixed $var): bool 関数が正しい挙動をすることを証明できるようになりました。 !!$var は常にboolean型なので、 $var がboolean型ではないときには !!$var === $varfalse に評価されます。 $vartrue のときも false のときも !!$var === $vartrue に評価されるので、この関数の挙動が正しいことを証明できました。

!! してから元々の値と比較するというような便利なことは他の型ではできそうにないので、もうちょっと一般化しておきましょう。

function is_bool($var): bool
{
    return (bool)$var === $var;
}

問2: is_int(mixed $var): bool

整数への変換Converting to integer)を読むと、boolean型, int型, float型, string型, resource型, null型の6つの型はint型に変換できるようです。array型, object型の2つの型をint型に変換しようとしたときの挙動は未定義と書いてあります。

循環呼び出しにならないように is_array() , is_object() を定義できると期待すると、次のような実装ができます。

<?php

namespace CarrotRakko;

/**
 * int型かどうか
 *
 * @param mixed $var
 * @return bool
 */
function is_int($var): bool
{
    return !is_array($var)
        && !is_object($var)
        && (int)$var === $var;
}

問3: is_float(mixed $var): bool

float への変換Converting to float)を読むと、float型への変換が定義されている型とint型への変換が定義されている型は同じことがわかります。したがって、 is_int() を実装したときと同じ期待のもとで次のような実装が可能です。float型には NAN という NAN !== NAN な値があることに注意が必要です。

<?php

namespace CarrotRakko;

/**
 * float型かどうか
 *
 * @param mixed $var
 * @return bool
 */
function is_float($var): bool
{
    return !is_array($var)
        && !is_object($var)
        && (
            (float)$var === $var
            || $var !== $var
        );
}

問4: is_string(mixed $var): bool

文字列への変換Converting to string)を読みましょう。array型とobject型があやしいので実験してみます。

(string)[];
// => PHP Notice:  Array to string conversion

(string)(new class() {});
// => PHP Fatal error:  Uncaught Error: Object of class class@anonymous could not be converted to string

諦めて is_int() , is_float() と同様の実装をします。

<?php

namespace CarrotRakko;

/**
 * string型かどうか
 *
 * @param mixed $var
 * @return bool
 */
function is_string($var): bool
{
    return !is_array($var)
        && !is_object($var)
        && (string)$var === $var;
}

問5: is_array(mixed $var): bool

配列への変換Converting to array)を読むと、8つの型はいずれもarray型に変換できるので、次の実装ができます。

<?php

namespace CarrotRakko;

/**
 * array型かどうか
 *
 * @param mixed $var
 * @return bool
 */
function is_array($var): bool
{
    return (array)$var === $var;
}

問6: is_object(mixed $var): bool

オブジェクトへの変換Converting to object)を読むと、8つの型はいずれもobject型に変換できるので、次の実装ができます。

<?php

namespace CarrotRakko;

/**
 * object型かどうか
 *
 * @param mixed $var
 * @return bool
 */
function is_object($var): bool
{
    return (object)$var === $var;
}

問7: is_resource(mixed $var): bool

リソースへの変換Converting to resource)を読むと、resource型に関しては今までと同じ戦略が通用しそうにありません。循環呼び出しにならないように is_null() を定義できる自信があるので、次のような実装でゴリ押します。

<?php

namespace CarrotRakko;

/**
 * resource型かどうか
 *
 * @param mixed $var
 * @return bool
 */
function is_resource($var): bool
{
    return !is_bool($var)
        && !is_int($var)
        && !is_float($var)
        && !is_string($var)
        && !is_array($var)
        && !is_object($var)
        && !is_null($var);
}

問8: is_null(mixed $var): bool

遊ぶ順番を間違えた感...。

<?php

namespace CarrotRakko;

/**
 * null型かどうか
 *
 * @param mixed $var
 * @return bool
 */
function is_null($var): bool
{
    return null === $var;
}

展望

関数を実装してみたはいいものの、エラーを吐かないかどうかや、挙動がビルトインの関数と同じかのテストをする必要があるなと思っています。

また、PHPには8つの型を判定する関数以外にも is_xxx() な名前の関数があるので、挙動を理解して再発明してみたいですね。

問9: is_a
問10: is_callable
問11: is_countable
問12: is_finite
問13: is_infinite
問14: is_iterable
問15: is_nan
問16: is_numeric
問17: is_scalar
問18: is_subclass_of

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

PHPで日本語のメールを送信する時のおさらい

(第〇弾なのかわからなくなってしまったが……)久々のおさらいシリーズ。

 最近、仕事で単発のメールフォームを作ったのだが、メール送信周りの処理をスクラッチしたところ、受信するメーラーによって文字化けしたり、迷惑メール化しちゃったりと結構色々大変だったので、そのノウハウをまとめておこうかと思った次第。
 まぁ、基本的に送信時のメールヘッダを「これでもか!」ってぐらい念入りに設定してあげるのがトラブル起こさない秘訣である。横着して簡素なメールヘッダにすると、たいていどこかでトラブルが起きるんだよね(苦笑)

PHPで日本語メールを送るならmb_send_mail()関数を使う

 日本語などのマルチバイト文字列が件名や本文に含まれるメールを送信する場合は、mb_send_mail()関数を使うのが安心である。この関数はmail()のラッパーで、実体であるmail()関数がメール送信処理を行う前に件名や本文を自動でエンコードしてくれる。いわゆるマルチバイト文字列フィルター付きのメール送信関数と云える。
 この関数を実行する前に、mb_language()関数で使用言語を指定し、mb_internal_encoding()で内部エンコードを指定しておけば、その指定に沿った言語セットとエンコード方式でメールのマルチバイト文字を自動変換してくれるので、件名や本文などを個別にエンコード処理するといった手間や処理ミスから解放されるので、心穏やかにメール送信処理を組めるようになるのだ。
 あと、多言語対応システムでメールを送信する場合に、言語や文字コードを変数化して切り替えることが容易なので、スッキリした汎用的なコードが書けるというメリットもある。

mb_language( 'Japanese' );
mb_internal_encoding( 'UTF-8' );
mb_send_mail( $send_to, $subject, $body, $headers );

 こんな風に使うのが一般的だ。

メールヘッダの一覧

 必要最小限のメールヘッダ定義だけでメール送信を行うと、受信するメーラーによって文字化けやら迷惑メールフィルタに引っかかったりと様々なケースが発生する。開発していると、動作検証するメーラーはPCのGmailとスマホのメーラーぐらいまでと、そこまで数多くのメーラーでの受信確認までは手が回らない。そのため、それら限定されたメーラー群では不具合が出なかったのでリリースしてみたものの、実際運用したら、やれこのメーラーだと文字化けした、このメーラーだと迷惑メールに分類されてしまっただのというケースがしばしば発生するのだ。
 そういう出戻りを極力避けるためにも、メールヘッダはきちんと念入りに設定しておくのが最善だ。たいていメール送信における問題は、メールヘッダの設定や調整でほぼトラブルシュートができるので、先んじてきちんと設定しておくのが重要になってくる。

 まずは、メールヘッダの種類と設定値をここで抑えておくことにする。コーディングの基本は「理解して」使うことが何よりも重要なのだ。
 下記一覧の「必要度」が「〇」なものがメールを送る際には設定しておくべきヘッダだ。「△」はなるべく設定しておいた方が良い必要度で、「-」は宛先のメール種別や転送を伴うか等必要に応じて使うヘッダとなる。まぁ、最低限「〇」と「△」のメールヘッダはすべて設定するようにしておけば、そうそうトラブルは起きないはずだ。

ヘッダ名 設定値の内容 補足 必要度
MIME-Version 英数字以外の文字や画像を取り扱える電子メールであることを示すMIMEのバージョン番号 現在は1.0のみ規定されているので固定値で1.0を指定する
Content-Transfer-Encoding メール転送中の符号化方式を指定する。初期値の7bitの他、8bitbinaryquoted-printablebase64が指定可能 日本語を取り扱う場合は初期値の7bitで良い。base64等にすると送信メールのデータ量が肥大化してしまうデメリットがあるので注意
Content-Type メールのメッセージ中のデータ形式。設定値の仕様は{type}/{subtype}; {parameter},...である テキストメールならtext/plain;、HTMLメールならtext/html;とし、パラメータとして文字コード指定(charset=UTF-8等)を付与しておくことを推奨する(※後述する)
Return-Path メールが届かなかった場合に、そのメールが送り返される返信先のメールアドレス。Reply-Toと異なり、メールサーバが付加するヘッダ値となる。別名「エンベローブ・フロム・アドレス」 指定しても、通常メールサーバによって上書きされてしまうが、強制的に指定値を押し通すことも可能(※後述する)
From (差出人の名前もしくは組織名と)送信元メールアドレス 複数指定可能。複数指定する場合はカンマ,で区切る。"{差出人名} <{送信元メールアドレス}>"もしくは{送信元メールアドレス}のみのいずれかで指定する
Sender 実際の(差出人の名前もしくは組織名と)送信元メールアドレス 複数指定不可。"{差出人名} <{送信元メールアドレス}>"もしくは{送信元メールアドレス}のみのいずれかで指定する
To (受信者の名前と)宛先のメールアドレス 複数指定可能。複数指定する場合はカンマ,で区切る。FromやSenderの書式が利用可能
Cc カーボンコピー先の(受信者の名前と)宛先メールアドレス 複数指定可能。複数指定する場合はカンマ,で区切る。FromやSenderの書式が利用可能 -
Bcc ブラインドコピー先の(受信者の名前と)宛先メールアドレス 複数指定可能。複数指定する場合はカンマ,で区切る。FromやSenderの書式が利用可能 -
Resent-From メール転送時の転送元メールアドレス 複数指定可能。仕様はFromやToと同等 -
Resent-Sender メール転送時の実際の転送元メールアドレス 複数指定不可 -
Resent-To メール転送時の転送先のメールアドレス 複数指定可能。仕様はFromやToと同等 -
Resent-cc メール転送時のカーボンコピー先のメールアドレス 複数指定可能。仕様はFromやToと同等 -
Resent-bcc メール転送時のブラインドコピー先のメールアドレス 複数指定可能。仕様はFromやToと同等 -
Subject メールの件名 mail()では別途引数化されているのでヘッダに指定する必要はない ×
Comments 任意のコメント -
Keywords メール本文において注目してほしいキーワード等 メーラーによっては指定されたキーワードをハイライトしてくれたりするらしいが、ほとんど実装されていない ×
Reply-To 返信先のメールアドレス 一般的に、未指定時はFromの値が返信先として使用される
In-Reply-To 返信時にどのメールへの返信かを示す値。Message-IDの値を指定する 複数指定不可 -
References 返信などで関係している他のメッセージの一覧。Message-IDの値を指定する 複数指定可能。多くのメーラーでこの値を元にスレッド表示を行っている -
Message-ID 個別のメールを特定するためのユニークID。{ユニークID}@{ドメイン名(FQDN)}という形式で指定する。 このIDは全世界で唯一なものにならなければならないと規定されている -
Date メールの作成(=送信)日時 メールサーバ側で付与するので設定不要 ×
Recieved メールを転送したメールサーバ情報を示す文字列 メールサーバが追加する文字列のため設定不要 ×
Encrypted 暗号化用の設定 実際には、必要に応じてメールサーバ側でメールの暗号化を行うので設定は不要 ×
Mail-Followup-To 主にメーリングリストで利用されるメーリングリストの宛先 複数指定可能 -
Mail-Reply-To 主にメーリングリストで利用される送信元のメールアドレス 複数指定不可 -
Organization 送信者が所属する組織名
Precedence メールサーバの配信優先度。list>junk>bulkの順に優先される メールサーバによってこの値を使うかどうかは異なるので、必ずしも設定した優先度通りに配信されるわけではない -
Return-Receipt-To 配送確認の返信先メールアドレス。メールサーバのメールボックスにメールが届いた際に、メールサーバ側がこの返信先へ返信を行う 開封通知とは異なるので注意 -
Errors-To エラー時の返信先メールアドレス 主にメーリングリストなどで利用される -
Disposition-Notification-To 開封通知の返信先メールアドレス -
X-***** X-で始まるメールヘッダは送信元が任意に設定可能な拡張ヘッダとなる このヘッダ値が解釈されるかは受信するメーラー側に依存するので、必須なヘッダではない ×
X-Sender 送信元のメールアドレスを示す メーラーごとの拡張仕様のため、使用されるかは受信したメーラー次第となる
X-Mailer 一般的には、送信元のメールソフト(メーラー・アプリケーション)の種類を示す メーラーごとの拡張仕様のため、使用されるかは受信したメーラー次第となる
X-UIDL POP利用時用のユニークID POPサーバが付加する値なので設定不要 ×
X-Priority メールの重要度。通常は 3 で、1(高)~5(低)で指定できる メーラーごとの拡張仕様のため、使用されるかは受信したメーラー次第となる
X-Msmail-Priority マイクロソフト製メーラー(Outlook等)用の重要度。仕様はX-Priorityと同じ メーラーごとの拡張仕様のため、使用されるかは受信したメーラー次第となる -

 いやぁ、今回メールヘッダの設定値を調べてみて、色々と勉強になった。今までそこまで意識もせずに漠然と設定してた自分に喝を入れたいものだ。

メールヘッダは省略しないで設定しよう

 ここからは実践編だ。前項のリストを元に、メールヘッダを省略せずに設定していこう。基本的にメールヘッダは改行コード区切りのプレーンな文字列として取り扱われるので、最終的に文字列としてmb_send_mail()に渡してあげる必要があるのだが、設定値が多いのでそのまま文字列として定義していくとコードの見通しが悪くなる。そこで、連想配列として定義して、最後に文字列展開するのがベストだと思う。

$headers = [
  'MIME-Version' => '1.0',
  'Content-Transfer-Encoding' => '7bit',
  'Content-Type' => 'text/plain; charset=UTF-8',
  'Return-Path' => 'from@example.com',
  'From' => 'SenderName <from@example.com>',
  'Sender' => 'SenderName <from@example.com>',
  'Reply-To' => 'from@example.com',
  'Organization' => 'OrganizationName',
  'X-Sender' => 'from@example.com',
  'X-Mailer' => 'Postfix/2.10.1',
  'X-Priority' => '3',
];
array_walk( $headers, function( $_val, $_key ) use ( &$header_str ) {
    $header_str .= sprintf( "%s: %s \r\n", trim( $_key ), trim( $_val ) );
} );
mb_send_mail( $send_to, $subject, $body, $header_str );

 mb_send_mail()関数に渡すメールヘッダ文字列の変数$header_strarray_walk()関数のクロージャに参照渡しされ、初回のクロージャ起動時に初期化されるのでグローバルスコープ下での初期化は不要だ。
 蛇足だが、連想配列をキー・バリュー形式の文字列に展開する時は、このクロージャを使うやり方がお手軽なので、覚えておくと重宝する。

Content-Typeヘッダには文字コードパラメータを含めること

 Content-TypeヘッダがContent-Type: text/plain;のようなタイプ指定だけだと、Thunderbird等のメーラーで本文が文字化けする場合がある。これは、メールヘッダの指定時に、mb_language( 'Japanese' )mb_internal_encoding( 'UTF-8' )で言語や文字コードを指定していても回避できない。おそらく、メーラー側でContent-Typeヘッダ値に文字コードがない場合は、ユーザが指定しているメーラーのエンコーディング指定を優先してしまうからと推測される。
 なので、対策としてはContent-Typeヘッダには優先的にエンコードする文字コードを制御するために文字コードパラメータを含めておくことである。

Content-Type: text/plain; charset=UTF-8

 日本語などのマルチバイト文字列を含むテキスト形式のメールを送信する場合であれば、上記のようにメールヘッダを指定しておくのが一番安全だ。

メールヘッダにマルチバイト文字列を含む場合はmb_encode_mimeheader()を使う

 mb_send_mail()関数がマルチバイト文字列を自動エンコードするのは「件名」と「本文」だけで、メールヘッダに含まれるマルチバイト文字列はエンコードされない。なので、メールヘッダに日本語を含むような場合は、それぞれ個別にエンコードしてあげないと文字化けが起こる場合がある(Outlookやスマートフォンのメーラー等で起きやすい)。そのため、mb_encode_mimeheader()関数を利用して、メールヘッダを個別にエンコードしていこう。

$headers = [
  'From' => mb_encode_mimeheader( '送信者の名前' ) .' <'. $from_email_address .'>',
  'Sender' => mb_encode_mimeheader( '送信者の名前' ) .' <'. $from_email_address .'>',
  'Organization' => mb_encode_mimeheader( '送信者の組織名' ),
];

 通常、メールヘッダ内でマルチバイト文字列を含む可能性があるのは上記のように「From」「Sender」「Organization」ぐらいなので、それらのメールヘッダを設定する場合には注意しておこう。一応、「To」や「Cc」「Bcc」等にも宛先の受信者名としてマルチバイト文字列を含むことができるが、システムから送信するメールについて、そこまでフランクな仕様を持ち込まなくても良いのではないかと思うので、今回はあえて割愛している。

Return-Pathヘッダに指定した値を強制的に適用する

 Return-Pathは別名「エンベローブ・フロム・アドレス」と云われ、メールサーバ(SMTPサーバ)側で指定されている値がメール送信時に割り振られる。そのため、PHP側でメールヘッダの値を設定しても通常は無視されてしまうのだ。さらに、メールサーバ側ではメール・アプリケーション(Postfixやqmail等)で最適なメールアドレスをReturn-Pathアドレスとして設定されていないと、初期値が使用されてしまう。例えばPostfixでは、postfix/canonicalで初期値が、

root root@localhost.localdomain

──となっているので、受信したメールのReturn-Pathヘッダのアドレスがroot@localhost.localdomain等の存在しない値になってしまう。このアドレスのドメイン部分が、メールが送信されたホストのDNSに登録されているSPFレコード(TXTレコードの一種)のドメインやIPアドレスと異なると、受信したメーラー側でSPF認証に失敗して「このメールは送信元をなりすましている可能性が高い」と判断して、迷惑メールに振り分けてしまうのだ。SPFレコードの詳細は、下記の記事が詳しくてわかりやすいので参照してみて欲しい。

送信ドメインを認証するためのSPFレコードに詳しくなろう

 さて、これを解決するには、まずメールサーバとしてのメール・アプリケーション側でReturn-PathアドレスをSPFレコードに登録されている最適なFQDNに変更するのが最善手である(なお、そもそもDNSにSPFレコードが設定されていない場合は、どうしようもないので、それは必ずやること)。しかし、そこまでするとなるとPHP以外の知見が必要になって来て何気に面倒なので、ここではお手軽にSPFレコードに登録されているFQDNのメールアドレスをReturn-Pathとして強制的に設定してあげる方法を紹介しておく。

$from_address = 'from@example.com';
$add_params = '-f'. $from_address;
mb_send_mail( $send_to, $subject, $body, $header_str, $add_params );

 mb_send_mail()関数では第5引数additional_parameterを使い、MTA(メール・アプリケーション≒sendmailもしくはmailコマンド)にコマンドライン引数を渡すことができる。これを使ってReturn-Pathアドレスを指定することで、MTAがメールヘッダに付与するReturn-PathアドレスをPHP側から強制化できるのだ。
 上記の例では、-fオプション(エンベローブ・フロム・アドレス設定オプション)のパラメータとして送信者のメールアドレスfrom@example.comをしている。これによって、MTAが送信するメールのReturn-Pathがfrom@example.comに固定化される。

 最後に、受信したメールのReturn-PathヘッダやSPFレコード認証を正常通過しているかを確認してみよう。Gmailで受信した場合での確認例になるが、受信したメールの右上の「その他」のアイコンから「メッセージのソースを表示」をクリックすると、メールヘッダが含まれる全ソースが表示される。

Return-Path: <from@example.com>
Received-SPF: pass (google.com: domain of from@example.com designates ***.***.***.*** as permitted sender) client-ip=***.***.***.***;

上記のように、Return-Pathヘッダが指定したメールアドレスになっていることと、Received-SPFが正常に認証されたことを示す「pass」となっていればOkだ。もしSPF認証に失敗していた場合は、SPFレコードに登録されているFQDNとReturn-Pathアドレスのドメインが一致していない可能性がある。

 おまけとして、任意のホストがSPFレコードを持っているかどうかをチェックするためのPHPスクリプトを紹介しておく。

<?php
$host_name = $_SERVER['HTTP_HOST'];// チェックしたいホスト名を指定する
$dns_records = dns_get_record( $host_name, DNS_ALL );
$result = '';
if ( ! empty( $dns_records ) ) {
    foreach ( $dns_records as $_record ) {
        if ( 'TXT' === $_record['type'] && strpos( $_record['txt'], 'v=spf1' ) !== false ) {
            $result .= "The \"{$host_name}\" has a SPF record(\"{$_record['txt']}\")." . PHP_EOL;
        }
    }
}
if ( empty( $result ) ) {
    $result .= "The \"{$host_name}\" does not have a SPF record." . PHP_EOL;
}
echo $result;
exit;

 変数$host_nameにReturn-Pathとして設定したいホスト名(FQDN)を指定して実行すると、そのホストのDNSがSPFレコードを持っているかを判定して、そのSPFレコードの設定値を表示してくれる。ただし、該当のホストがSPFレコードを持っていたとしても、その設定値でメーラー側のSPF認証を通過できるかまでは検証しないので、その点は留意してほしい。

MTAのTLS通信設定を確認しておく

 Gmail等では、メール受信時にTLS通信がされていないメールについては警告が出る。今のところこれによって迷惑メールに分別されることはないようだが、将来的にはわからない。なので、利用しているMTAで外部へのSMTP通信時にTLS通信を使うように設定しておくと安心である。
 例えば、Postfixでは外部へのSMTP通信時にTLSが利用可能ならTLS通信を使う設定が、デフォルトでOFFになっているので、有効化しておこう。Postfixでのやり方は下記の記事が参考になるかと。

Postfix で MTA 間送信に TLS を利用する

メール本文をテンプレート化して外部ファイルで管理する

 PHP側の処理に応じてメールの本文を変更したい場合などは、メール本文をテンプレート化して外部ファイルとして管理すると、ソース内にメールメッセージをハードコーディングすることもなく見通しの良いコードが書ける。例えば多言語対応したアプリケーションで、日本語環境ユーザと英語圏ユーザとで送信メールの記述言語を切り分けたい時など、このテンプレート方式にしておくとメール送信周りは汎用コード化でき、メールメッセージへの保守はテンプレート側だけに集約できるので、運用管理が抜群にしやすくなる。
 では、下記のようなテンプレートをメール本文として読み込んでみよう。

templates/mail_body.php
<?= $display_name ?>さん、こんにちは。

あなたのアカウントが新たに作成されました。
下記のURLから、ユーザー名:<?= $user_id ?>とご登録時のパスワードにてサインインしてください。

サインインURL:
<?= $signin_url ?>

〇〇〇〇事務局より

 次に、読み込み元での処理だ。

// 変数定義
$display_name = 'テスト・ユーザー';
$user_id = 'test_user1';
$signin_url = 'https://example.com/signin/?uid=' . $user_id;
// テンプレート読み込み
ob_start();
include __DIR__ . '/templates/mail_body.php';
$mail_body = ob_get_contents();
ob_end_clean();
$mail_body = strtr( $mail_body, [ "\r\n" => "\n", "\r" => "\n" ] );// to all LF

 ポイントはテキスト形式であるテンプレートをバッファリングによって読み込むことで、変数の埋め込み処理が完了した状態のテキストを、出力を伴うことなくメール送信用の$mail_bodyとして格納できる点だ。コードの見通しも良いし、読み込むテンプレートファイルを条件分岐させることも容易だ。
 あと、最後に改行コードをすべてLFに変換しているが、これは必須ではない。稀に、テンプレートファイルで読み込んだ本文中の改行コードが、メール送信時に重複解釈されて空行が増えたり、逆に改行されないというケースが発生した場合の対処用のコードである。詳しくは、PHPのmail()のドキュメントで下記のように注意されている。

メッセージが受信されなかった場合には、LF(\n)のみを使ってみてください。 Unix の MTA の中には、自動的に LF を CRLF に変換してしまう もの (有名なところでは、qmailなど) があります(もし CRLF を利用していた場合、CR が重複してしまいます)。 ただし、これは最後の手段です。というのも、これは RFC 2822に違反しているからです。

 本当はテンプレート読み込み型のメール送信時に改行コードに異常が見られた場合にのみ使った方が良いのだろうが、私は面倒なので一律で変換している。


 今回のおさらいはここまで。

 最後に、このおさらいで紹介した一連の手順をふまえて、mb_send_mail()のラッパーとして拡張したメール送信処理を私の個人ブログに掲載してあるので、興味がある人は覗いてみてくださいな。

#実践的なメール送信処理の例

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

phpのarrayキャスト

(array)の挙動を確認します。

  • 文字列から配列に展開されるか?
  • 配列は二重配列になるか?

phpのマニュアル

// 配列と文字列は異なる値です
var_dump(["a"] === "a");

// 文字列が配列に変換され一致します
var_dump(["a"] === (array)"a");

// 元から配列なら変換されません
// 配列で囲まれるわけではありません
// 型の変換なので、配列型は配列型のままでした
var_dump(["a"] === (array)["a"]);

// 配列と文字列なので当然異なる値です
var_dump(["a", "b", "c"] === "abc");

// もしかして複数文字は配列に展開される? ["a", "b", "c"]
var_dump(["a", "b", "c"] === (array)"abc");

// そんなわけありません。
// c言語のchar型の配列と間違っていました。
// 文字列を配列にキャストすると項目が1つの配列になりました。
var_dump((array)"abc" === ["abc"]);

// 元から配列なら変換されません
var_dump(["a", "b", "c"] === (array)["a", "b", "c"]);

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

phpのarrayキャストで元々arrayが二重arrayになるわけではない

すいません。マニュアルをよく読めば書いてあることです。

(array)の挙動を確認します。

  • 文字列から配列に展開されるか?
  • 配列は二重配列になるか?

phpのマニュアル

// 配列と文字は異なる値です
var_dump(["a"] === "a"); // false

// 文字が配列に変換され一致します
var_dump(["a"] === (array)"a"); // true

// 元から配列なら変換されません
// 配列で囲まれるわけではありません
// 型の変換なので、配列型は配列型のままでした
var_dump(["a"] === (array)["a"]); // true

// 配列と文字列なので当然異なる値です
var_dump(["a", "b", "c"] === "abc"); // false

// もしかして複数文字は配列に展開される? ["a", "b", "c"]
var_dump(["a", "b", "c"] === (array)"abc"); // false

// そんなわけありません。
// c言語のchar型の配列と間違っていました。
// 文字列を配列にキャストすると項目が1つの配列になりました。
var_dump(["abc"] === (array)"abc"); // true

// 元から配列なら変換されません
var_dump(["a", "b", "c"] === (array)["a", "b", "c"]); // true

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

phpの「=>」(ダブルアロー演算子)と「->」(アロー演算子)

「=>」:ダブルアロー演算子

連想配列のこの「キー」にこの「値」を入れますよー
というのを表すための演算子。

phpの
ダブルアローがわからなければこれを読むんだ!!

http://raichel.hatenablog.com/entry/2015/01/15/012116

「->」:アロー演算子

インスタンスの持つ、プロパティにアクセスするための演算子。

この記事で私は理解した。

https://techacademy.jp/magazine/19296

インスタンスは「クラスを実体化したもの」。
インスタンスがわからないときには、オブジェクト指向を学ぼう。

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

LaravelでJSONリクエストを受け取る

前提条件

eclipseでLaravel開発環境を構築する。デバッグでブレークポイントをつけて止める。(WindowsもVagrantもdockerも)
本記事は上記が完了している前提で書かれています
プロジェクトの作成もapacheの設定も上記で行っています

Controllerにメソッド追加

(1) /sample/app/Http/Controllers/SampleController.phpにrequestJson1メソッド、requestJson2メソッドを追記

    public function requestJson1(Request $request)
    {
        return view('sample.requestJson');
    }

    public function requestJson2(Request $request)
    {
        $data = [
            'a' => $request->input('a'),
            'b' => $request->input('b.bb'),
            'c' => $request->input('c')['cc']
        ];
        return $data;
    }

(2) /sample/routes/web.phpに下記を追記
Route::get('sample/request-json1', 'SampleController@requestJson1');
Route::post('sample/request-json2', 'SampleController@requestJson2');

viewの作成

/sample/resources/views/sample/requestJson.blade.phpファイル作成

requestJson.blade.php
<html>
    <head>
        <title>sample</title>
        <script type="text/javascript">
            function ajax(){

                var form = document.getElementById("sampleForm");

                var reqData = {"_token": null, "a": null, "b": {"bb": null}, "c": {"cc": null}};
                reqData._token = form._token.value;
                reqData.a = form.a.value;
                reqData.b.bb = form.b.value;
                reqData.c.cc = form.c.value;

                var req = new XMLHttpRequest();
                req.open(form.method, form.action);
                req.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
                req.responseType = 'json';
                req.send(JSON.stringify(reqData));
                req.onload = function () {
                    var json = req.response;
                    alert("a:" + json['a']  + "\n" + "b.bb:" + json['b']+ "\n" + "c.cc:" + json['c']);
                }
            }
        </script>
    </head>
    <body>

        <form id="sampleForm" action="{{ url('sample/request-json2') }}" method="post"  onsubmit="ajax(); return false;">
            @csrf
            <input type="text" name="a" >
            <input type="text" name="b" >
            <input type="text" name="c" >
            <input type="submit" >
        </form>

    </body>
</html>

Laravelでjsonリクエストを受け取るようにするために
req.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
を書きました
LaravelのControllerクラスはjsonを受け取るからと言って特別な記載は要りません
Content-Typeヘッダプロパティにapplication/jsonが指定されていれば、inputメソッドで値を取得できます

動作確認

http://localhost/laravelSample/sample/request-json1

インプットフォームに値を適当に入力して送信ボタンをクリック
インプットフォームに入力した値がalertで表示された

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

laradock環境下のmysqlで文字化けしちゃった

はじめに

laradockで環境構築を行ってからseederでデータ投入。
その後にmysqlコンテナに入り、データが入っているか確認すると英語や数字は問題なく表示された。が、
日本語のデータが文字化けしていた。
それを解決するのにハマってしまったので、備忘録としてこの記事を書きたいと思う。

原因

mysqlにてstatusコマンドで文字コードを確認。
Server characterset: utf8
Db characterset: utf8
Client characterset: latin1
Conn. characterset: latin1

上記にあるように
Client characterset: latin1
Conn. characterset: latin1

ClientとConnの文字コードがlatin1になってたのが原因だとわかった。

解決策

latin1をutf8にするためにlaradock/mysql/my.cnfに下記を追加した。
character-set-server=utf8
[client]
default-character-set=utf8

それから
docker-compose stop mysql
docker-compose down build mysql
docker-compose up -d mysql

でmysqlコンテナを再起動すると解決した。

参考サイト

https://qiita.com/luccafort/items/0553c589dcc6459746bc

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

PHPの環境構築

MAMPのインストール

https://www.mamp.info/en/

各種設定

  • MAMPのPreferanceから使用しているPHPのバージョン確認
  • Set Web & MySQL ports to 80 & 3306クリックでポート変更
  • 「アプリケーション」→「MAMP」→「bin」→「php」→「使用中のバージョン」→「conf」→「php.ini」
php.ini
;date.timezone = "Europe/Berlin"

以下に変更

php.ini
date.timezone = "Asia/Tokyo"

動作確認

適当なファイルを作ってMAMP起動する

index.php
<?php
echo "Hellow World";

以上。

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

【Laravel】.env.testingの使用方法と注意点

LaravelでPHPUnitを使うときにテスト用の`.env`を設定するのにハマったので記事にしました。

環境

Docker 19.03.8
PHP 7.3.16
Laravel 6.18.3

.env.testingの設定方法

本番DBを汚さないようにPHPUnitを実行する時のみテスト用のDBを使うようなことはよくあると思います。

その方法の一つとして.env.testingを作成し、phpunitにその設定値を読み込ませるやり方があります。

.env.exampleをコピーして.env.testingを作成し、以下の箇所を変更します。

env.testing
APP_ENV=testing // testingに変更
APP_KEY= // 空にしておく

// 接続したいDBの情報を設定する
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=test_database
DB_USERNAME=root
DB_PASSWORD=

そうしたらphpunit.xmlを開き、APP_ENVの値をtestingに変更します。

phpunit.xml
<php>
    <env name="APP_ENV" value="testing" force="true"/>
    <env name="DB_CONNECTION" value="mysql"/>
    <env name="BCRYPT_ROUNDS" value="4"/>
    <env name="CACHE_DRIVER" value="array"/>
    <env name="MAIL_DRIVER" value="array"/>
    <env name="QUEUE_CONNECTION" value="sync"/>
    <env name="SESSION_DRIVER" value="array"/>
</php>

最後に.env.testingに新規にアプリケーションキーを設定したら完了です。
これをしないと.envAPP_KEYを参照しに行ってしまい、本番用のDBのデータが全部飛ぶので注意しましょう。

php artisan key:generate --env=testing

ハマったポイント

docker-compose.ymlなどで環境変数にAPP_ENVDB_DATABASEなどの設定をしている場合、注意しなくてはいけない点があります。
phpunit.xmlで環境変数を上書きする際に、デフォルトだと<server>タグになっていますが、これを<env>タグに変更する必要があります。

phpunit.xml
<php>
    <server name="APP_ENV" value="testing" force="true"/>
    <server name="DB_CONNECTION" value="mysql"/>
    ...
</php>
phpunit.xml
<php>
    <env name="APP_ENV" value="testing" force="true"/>
    <env name="DB_CONNECTION" value="mysql"/>
    ...
</php>

<server>タグと<env>タグは以下のような違いになっています。

$_SERVER['APP_ENV'] = 'testing'; // <server>
$_ENV['APP_ENV'] = 'testing'; // <env>

今回はdocker-compose.ymlで環境変数としてAPP_ENVなどを定義していたので、さらにそれを上書きする必要がありました。

参考文献

Laravel 7.x テスト: テストの準備
Laravel × Docker でテスト用のデータベースコンテナを使う

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

ControllerからViewへの変数受け渡し

概要

Laravelの諸機能について忘れないようにアウトプットする自分用メモ。

ControllerからViewへの変数受け渡し

参考:https://qiita.com/ryo2132/items/63ced19601b3fa30e6de
参考:https://qiita.com/_Mizuki/items/df3a62fbe18bbbbc9ced
参考:http://blog.tojiru.net/article/230164798.html

Controllerからviewに値を渡す場合、ベターな方法が複数あるらしいので調べた。

phpのcompact関数

viewに値を渡す際、compact関数を使用する方法がわりとベターらしい。
compact関数の理解が怪しかったので確認した。
公式の解説では変数名とその値から配列を作成する。とのこと。

公式の例

<?php
$city  = "San Francisco";
$state = "CA";
$event = "SIGGRAPH";

$location_vars = array("city", "state");

$result = compact("event", $location_vars);
print_r($result);
?>

// result 
Array
(
    [event] => SIGGRAPH
    [city] => San Francisco
    [state] => CA
)

良くわからん...。
自分なりに分かりやすかった例。

$arr = array(
  'apple' => $apple,
  'orange' => $orange,
  'lemon' => $lemon,
);
// ↑
// 同じ意味
// ↓
$arr = compact('apple', 'orange', 'lemon');

連想配列のkeyと変数名を勝手に紐付けてくれるらしい。

Controllerからviewへの変数の受け渡し

viewに値を渡すときにcompact関数を使用する場合は以下のようにするといい。

public function fruit()
{

  $apple  = 'apple';
  $orange = 'orange';
  $lemon  = 'lemon';

  return view('fruit', compact('apple', 'orange', 'lemon'));
}

Laravel独自のコレクションなのかと思ってたけど、そういうワケじゃないんですね...。
そもそもviewヘルパ関数がどんなだったっけと思って見直した。

第二引数に連想配列渡すだけなんだ。それを作りやすいからcompact使っているだけ。
つまりこの書き方でも一緒です。

public function fruit()
{
    $apple  = 'apple';
    $orange = 'orange';
    $lemon  = 'lemon';
    $fruit = ['apple'=>$apple, 'orange'=>$orange, 'lemon'=>$lemon];

    return view('fruit', $fruit);
}

withメソッド

Controllerからviewへの受け渡しにwithメソッドを使用する場合。

public function fruit()
{
    $apple  = 'apple';
    $orange = 'orange';
    $lemon  = 'lemon';

    return view('fruit')->with('apple', $apple)->with('orange', $orange)->with('lemon', $lemon);
}

もしくはこう

public function fruit()
{
    $apple  = 'apple';
    $orange = 'orange';
    $lemon  = 'lemon';

    return view('fruit')->with([
        "apple"  => $apple,
        "orange" => $orange,
        'lemon'  => $lemon,
     ]);
}

ちょっと可読性悪いですね。

個人的ベスト

  • 配列を渡す

状況にもよりますが、配列作って渡してあげる方法が一番好きです。
viewヘルパ関数の第二引数が大きくなるのはあまり好きではない。

// 画面にフルーツを渡す
public function fruit()
{
    $fruit = $this->returnfruit();

    return view('fruit', $fruit);
}

// フルーツの配列を返す
private function returnfruit()
{
    $apple  = 'apple';
    $orange = 'orange';
    $lemon  = 'lemon';

    return compact('apple', 'orange', 'lemon');
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPのstaticの理解〜初心者用〜

はじめに

PHPを勉強し始めると、すぐに出てくるstaticというキーワードですが
自分が理解をするのに時間がかかってしまったので
アウトプットも兼ねてまとめていきます。

staticとは

インスタンスを作らずにクラス変数もしくは、
クラスメソッドを宣言すればアクセスすることができます。

staticは、インスタンス(個別の実体)ではなく
クラス側にプロパティやメソッドを持たせることができます。
クラス側に持たせた変数は「クラス変数」
関数を「クラスメソッド」といいます。

注意点

1.staticの変数はオブジェクトからのアロー演算子->でアクセス不可
2.sutaticではないメソッドをstaticをつけたものと同じ方法で呼び出すとエラーになる

詳しくは、PHPドキュメントのこちらをご確認ください。

クラス変数やクラスメソッドの呼び出し方

クラス変数やクラスメソッドでは、アロー演算子->が使えないため
->の代わりにスコープ定義演算子(::)を
$thisの代わりにselfを使います。

Nekoクラスにクラス変数とクラスメソッドを使ってみます。

test.php
<?php

<?php

class Neko {
    public static $type = "飼いネコ";
    public $suffix = "";

    public $hp = 20;
    public $power = 10;

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

    function name() {
        return $this::$type . "の" . $this->suffix;
    }

    function attack($owner) {
        print $this->name() . "が" . $owner . "を攻撃し" . $this->power . "ポイントのダメージを与えた!" . PHP_EOL;
    }

    //クラスメソッド
    static function description() {
        print self::$type . "は、凶暴だが最愛の家族だ。" . PHP_EOL;
    }
}

Neko::description();

$neko_A = new Neko("モカ");
$neko_B = new Neko("カフェ");

$neko_A->attack("飼い主");
$neko_B->attack("他のネコ");

コンソール入力

$php tex.php

実行結果

飼いネコは、凶暴だが最愛の家族だ。
飼いネコのモカが飼い主を攻撃し10ポイントのダメージを与えた!
飼いネコのカフェが他のネコを攻撃し10ポイントのダメージ

クラスの説明をする関数
description()という名前のクラスメソッドを用意しました。

Neko::description();と書いて呼び起こしていますが
アロー関数->を使うとどうでしょう。

// Neko::description(); だったものを
Neko->description();

実行結果

PHP Parse error:  syntax error, unexpected '->' (T_OBJECT_OPERATOR)

となり、変なところに->があるねん!という内容になります。
なので、staticをつけた変数や関数はクラス名::○○と書く必要があるということですね。(変数を参照したい場合はスコープ定義演算子の後に$を入れてください)

まとめ

スコープ定義演算子が出てきた時は、
アロー演算子を覚えたばかりだと戸惑ってしまいますが
staticを使った関数の場合はアロー演算子ではなく
スコープ定義演算子を使うと覚えれば良さそうです。


・クラス名::○○ static宣言されたメソッドやプロパティへアクセスする
・self::○○ 自クラスのクラス変数、クラスメソッドへアクセスする
・parent:: 指定した親クラスを示す
・インスタンス変数::○○ (self::○○) インスタンスが持つクラス変数やクラスメソッドへアクセスする

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

PHPのstatic とは、静的とは

static(静的)概要

PHP Manualより

クラスプロパティもしくはメソッドを static として宣言することで、 クラスのインスタンス化の必要なしにアクセスすることができます。 static なプロパティは、インスタンス化されたクラスオブジェクトから アクセスすることはできません

つまりクラス外から staticメソッド(静的メソッド)にアクセスする場合、
Foo::staticではこのメソッドにアクセスできるが、foo->static()の形ではアクセスできない。

staticメソッド(静的メソッド)についての備忘

  • 静的(静的メソッド、静的プロパティ)というのは、インスタンス化しないで使うということ
  • なのでインスタンスに影響を受けない(保守しやすい)
  • 基本宣言されたクラス内で使用するケースが多い(self::xxxの形)
  • 平たく言うと、インスタンス化が必要な時は、staticはつけない、インスタンス化が不要な時はstaticをつける
  • アクセスする際は、::を使用する。
  • ::はスコープ定義演算子と言う
  • 無闇にインスタンスを実装して保守範囲を広げないために静的を利用する

staticメソッド(静的メソッド)にアクセスする例

sample.php
<?php

class Foo {
    public static function aStaticMethod() {
        echo "Hello Static!!!";
    }
}

Foo::aStaticMethod();
//or
$classname = 'Foo';
$classname::aStaticMethod();
//出力結果
"Hello Static!!!";

//staticなメソッドには->ではアクセスできない
$foo = new Foo();
$foo->aStaticMethod;
//出力結果
PHP Notice:  Undefined property:xxx
?>

参考

PHP: 静的メソッドは何のためにあるか?
self::と$this->の使い分け
PHP - What is OOP?

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

PHPで画像をアップロード

学習をしていく中で、理解が難しかったので、要点をまとめてみました。

まず前提として、HTMLでフォーム画面に以下の記述が必要になります。

<input type="file" enctype="multipart/form-data" method="post" name="img">
//nameは任意
//enctypeは画像の情報を送信するのに必要なおまじないと思ってください。

画像をアップロードする流れ

①移動先のディレクトリを変数に格納する
②一時的に保存するファイルを変数に格納する
③アップロードしたい画像のファイル名を変数に格納する
④move_file_uploads関数でアップロードする

①移動先のディレクトリを変数に格納する

$uploads_dir = './uploads';

のちの記述をすっきりさせるために格納させています。

②一時的に保存するファイルを変数に格納する

$tmp = $_FILES['img']['tmp_name'];
//imgはinputで記述したnameの中身です
var_dump($tmp);
//string(14) "/tmp/phpimzszk"

tmp_nameとはアップロードした画像ファイルをサーバー上で一時的に保存する場所のファイル名です

③アップロードしたい画像のファイル名を変数に格納する

$name = basename($_FILES['img']['name']);
//imgはinputで記述したnameの中身です
var_dump($name);
//string(9) "melon.jpg" 画像ファイル

basename関数でファイル名を取得します。この記事がかなり分かり易かったので参考にしました。
https://www.flatflag.nir87.com/basename-844
$FILES['img']['name']がファイル名です

④move_file_uploads関数でアップロードする

move_file_uploads($tmp, "$uploads_dir/$name");
//move_file_uploads(もとにあるファイル名, 移動先のファイル名)

詳しくはこちらをご参照ください
https://www.php.net/manual/ja/function.move-uploaded-file.php

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

PHP 初心者向け実行環境(Local by Flywheel)と練習問題

対象者

初めてプログラミングを学習したい方
(用語の解説等はあえてしません)
※わからないことはすぐに調べる癖をつけよう!

Windows 10 64bit

事前準備

  • Visual Studio Codeの導入(IDE)
  • Local by Flywheelの導入

Visual Studio Code(IDE)

https://azure.microsoft.com/ja-jp/products/visual-studio-code/
ここからVisual Studio Codeをダウンロード

インストールが終わったらプラグインを導入しよう

最低限欲しいプラグイン

  1. Japanese Language Pack for VS Code 日本語化のプラグイン
  2. Prettier コードのフォーマット自動でおこなってくれる

Local by Flywheel

環境構築がめちゃくちゃ手軽なので選びましたが、XAMPPでもおっけーです。

こちらを参考に

PHPを実行する

まで進めて動作確認をしよう

 練習問題

環境構築が既に終わってる方はこちらから

ちなみにですが、模範解答はまた後日

その1

加算演算子を使用して以下の変数を計算して下さい

study01.php
 $a = 2
 $b = 3
 $c = "答え"

その2

以下の変数とif, else if, else とループ文(for, foreach等)を使用し
50点未満なら「不可」
50点以上65点未満なら「可」
65点以上80点未満なら「良」
80点以上なら「優」というコードを作成してください。
70点の場合の結果を表示させてください。

study02.php
<?php
$result = array("不可", "可", "良", "優");

?>

その3

study03.phpstudy04.phpを流用して、
if文とループ処理分(for, foreach等)を使い「"言語名"は"難易度"です」と表示してください。
例: C言語を選択したとき
   C言語は難しいです
※プログラムの難易度は実際と違います

C言語 = 難しい
Java = 普通
JavaScript = 優しい

study03.php
<html>
<head>
    <title>プログラムの難易度</title>
</head>
<body>
    <form action="study04.php" method="post">
        習得したいプログラム言語を選択してください<br>
        <input type="radio" name="choice" value="1">C言語
        <input type="radio" name="choice" value="2">Java
        <input type="radio" name="choice" value="3">JavaScript
        <input type="submit" value="送信">
    </form>
</body>
</html>
study04.php
<html>
<head>
    <title>プログラム言語難易度</title>
<body>
    あなたが選んだプログラム言語は
    <?php
    $program = array(
    "1" => "C言語", 
    "2" => "Java", 
    "3" => "JavaScript"
    );
    $difficult = "難しい"; // C言語 = 難しい
    $normal = "普通"; // Java = 普通
    $easy = "易しい"; // JavaScript = 優しい

   // ここにコーディング

    ?>
    です。
</body>
</html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPにてPDOでMySQLに接続して、複数のSQLを実行する

1つのPHPファイル内に、INSERTとSELECTのSQLを記載する方法です。
DB接続1回で複数のSQLが実行できます。
2個目のSQLでは配列$dataの値をクリアすることがポイントです。

以下のソースコードは、ユーザーID登録実行画面の想定です。
前画面のユーザーID登録確認画面からユーザー名を受け取り、DB登録しています。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <meta name="description" content="XXXX">
  <title>XXXX</title>
  <link rel="stylesheet" href="/main.css">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
</head>

<body>
<?php

//データベースの接続エラー対策
try
{
  // POSTメソッドで前の画面の入力値を取得する
  $user_name=$_POST['user_name'];

  //データベースに接続する
  $dsn = 'mysql:dbname=XXXX;host=mysqlXXXX';
  $user = 'XXXX'; //''内にはユーザ名を入力
  $password = 'XXXX';  //''内にはパスワードを入力
  $dbh = new PDO($dsn, $user, $password);
  $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

  //7桁数値をランダムで生成する
  $user_id= rand(100000,9999999);

  //ランダムで生成した7桁数値の存在チェック。$user_idがDBに存在する場合、再度ランダム数値を生成する
  $sql ='SELECT COUNT(*) as cnt FROM XXXX_table WHERE user_id=?';
  $stmt = $dbh->prepare($sql);  // prepareメソッドでSQLをセット
  $data[] = $user_id; // 1つ目の?にセットしたいデータが入っている変数を書く
  $stmt->execute($data);  //executeでDBへの処理要求を実行
  $rec=$stmt->fetch(PDO::FETCH_ASSOC);

  // SQL分を使ってレコードを追加
  $sql ='INSERT INTO place_data_table(place_id) VALUES (?)'; //'INSERT INTO~'を変数$sqlに格納する
  $stmt = $dbh->prepare($sql); // prepareメソッドでSQLをセット
  $data = []; // 配列$dataの値をクリア
  $data[] = $user_id; // 1つ目の?にセットしたいデータが入っている変数を書く
  $stmt->execute($data);  //executeでDBへの処理要求を実行

  // データベースから切断する
  $dbh = null;

  //結果を表示
  print $user_name;
  print 'を追加しました。<br />';

}
catch (Exception $e)
{
  /* ?><pre><?= print_r($e, true); ?></pre><?php デバッグの時にはコメントアウトを外す。エラー内容、問題が発生した行番がわかる*/
  print 'ただいま障害により接続できません。';
  exit(); //強制終了の命令
}
?>

<a href="user_list.php">戻る</a>

</body>
</html>

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

有料のWebサービスをリリースするまでに取り組んだこと・知見をまとめました【個人開発】

1. 作ったサービス

twikeshi-ogp.png

ツイ消し職人
https://twikeshi.net/

ツイ消し職人は大量のツイートを消したい方向けのツールです。
3,200件を超えるツイートを一括削除できます。
無料のツールなどでうまく削除できなかった方は是非ご利用ください。
既存のフォロワーをそのままに、Twitterをやり直すことができます。

2. 自己紹介

こんにちは、ひろと申します。
今年の3月に会社を辞め、現在はフリーランスエンジニアとして活動しています。

メガバンクのシステムエンジニア →
広告代理店(東証一部上場企業)のWebエンジニア →
フリーランスエンジニアという経歴です。

仕事でコードを書き始めたのは前職からで、プログラミングの経験年数は2年と9ヶ月くらいです。

3. なぜ作ったのか

私は今年の3月にフリーランスとして独立しました。
それに伴い、学生時代から使っていたTwitterアカウントの運用を変えようと思い、今までのツイートを削除してやり直すことにしました。
アカウントを作り直す選択肢もあったのですが、フォロワーを減らしたくなかったため、ツイ消しの道を選びました。
調べてみるとツイートの一括削除ツールがいくつか見つかったため、それを使ってツイ消しをすることにしました。

しかし、既存のツールではツイートの削除ができませんでした。
私の今までのツイート数は19万件で、Twitterアーカイブをダウンロードしたところzipファイルのサイズはなんと31GB。スマホの7GBプランなら4.5ヶ月分の通信量が必要になってしまう、とんでもない容量です。
そう、私がツイ廃だったのが全ての原因です。

普通のツイ消しサービスは、API制限の関係で3,200ツイートが削除の上限となってしまいます。私の19万ツイートに対してはあまりに無力すぎました。

もちろん、API制限を回避するためにTwitterアーカイブをアップロードして削除を行うサービスもあります。
しかし、31GBのzipファイルを送りつけると必ず500エラーが返ってきてしまい、私の試した範囲では、まともに動くものはありませんでした。海外の有料サービスでさえダメでした。(具体的なサービス名は出しませんが、日本円で1,600円払いました。手痛い出費です)

そこで私は、「ツイ廃でもツイートを削除できるサービス」が必要だと思い、ツイ消し職人の開発を始めました。

4. リリースするまでに取り組んだこと

取り組んだ全てのことを記載しています。

一. サービスの命名

最初は「ツイートクリーナー」という名前にしていました。
開発中盤に「ツイ消し職人」という名前を思いつき、変更しました。
ランサーズなどで募集するのも良いと思います。

二. ドメインの取得

ムームードメインでtwikeshi.netを取得しました。
欲しいドメインが埋まっている場合は、twikeshi-app.netのように工夫するのも良いと思います。

三. 商標権の取得

今回はお金がなかったので保留しています(いつでも取れるように商標調査は終えています)。
今はToreruなどの便利なサービスがあり、ものすごく簡単に出願できます。
48,000円で5年間有効になります。
商標権の取る取らないを選択するのは自由ですが、後から商標を第三者に取得されて商標権の侵害警告を受けた場合、サービス名やドメインを変える必要があるリスクは認識しておく必要があります。

四. プライシング

海外の同じようなサービスを参考に値付けを行いました。
現在は700円(税込)で提供しています。

その後、プライシングに関する本を3冊読んで(この本この本この本)考え方が変わったので、そのうち値上げするかもしれません。

どんなに高くても、その価格で買いたい人がいます。私自身も、このサービスを他の人が作っていたとしたら、喜んで利用していました。

間違っても、本来ターゲットでない人を取り込むために値下げするのはやめてください
例えば、私は友人達に「ツイ消し職人の適正価格はいくらだと思う?」と質問をすると、2人が「100円」と答えました。
しかし、断言しますが彼らは100円でも絶対に利用しません。何故ならば、彼らはこのツールの価値を理解していないからです。ツイ消しをしようと思ったことがない人に相場感を聞いても意味がありません。
逆に、本っっ当にツイ消しをしたくて困っている人からすれば、このツールが例え1万円でも喜んでお金を払うはずです。

五. 技術選定

1. バックエンド

バックエンドはLaravelで開発しました。
私は前職でSpring Bootを使っていましたが、このフレームワークではTwitterログインを実装するのに苦労しそうだったため、一からLaravelを学ぶことにしました。
Railsと悩みましたが、後述する理由によりレンタルサーバーで運用したかったので、Railsは諦めました。
Laravelはコードもドキュメントも読みやすいため、使っていて楽しいですね。
ドットインストールのLaravel入門がとても分かりやすかったのでオススメです。

役割 技術
PHPフレームワーク Laravel
データベース MySQL
Twitterログイン Laravel Socialite
Twitter APIライブラリ TwitterOAuth
メール送信 SendGrid

2. フロントエンド

CSSフレームワークにはMaterializeを採用しました。これも初めて使ったのですが、ドキュメントが分かりやすく情報量も多いのでオススメです。
今回はフロントで処理をする必要が無かったので、基本的にJavaScriptは使っていません。ファイルアップロードの画面は、アニメーションを付けるためにVue.jsを使いました。Vue.jsは以前から使っていたので、特に困ることはありませんでした。
次はNuxt.jsに挑戦するために勉強中です。

役割 技術
CSSフレームワーク Materialize
JavaScriptフレームワーク Vue.js
決済 Stripe Checkout

3. インフラ

バックエンドの項目で触れましたが、サーバーにはレンタルサーバーを採用しています。

役割 技術
レンタルサーバー エックスサーバー

Heroku / VPS / AWS EC2 / GCP App Engineなどの選択肢もありましたが、主にコストと運用の観点から除外しました。個人開発は自分でインフラを選べるのが良いですね。
今後もどんどんサービスを作っていく予定なので、サーバー費がかさむのはイヤだし、サービスを作る度に環境を構築するのも避けたかったのです。

もちろん要件によってはレンタルサーバーが使えない場合もあります(ミドルウェアの設定変更や追加インストールが必要な場合など)。
rootユーザーが使えないと困る場合は、状況に応じて各サービスを比較検討しましょう。
サーバーはHerokuだけどストレージにはAWS S3を使って、DBにはGCP Cloud SQLを使うといったトリッキーなこともできます。柔軟な発想で最適な構成を作りましょう。

参考に、私の考える主なインフラサービスのメリット・デメリットをまとめておきます。
※App Engineは詳しくないので簡易的な記載になってます

インフラサービス メリット デメリット
VPS(IaaS) ・安い
・root使える
・借りる度にお金がかさむ
・環境構築や設定が必要
EC2(IaaS) ・root使える
・マイクロサービス沢山ある
・高い
・借りる度にお金がかさむ
・環境構築や設定が必要
App Engine(PaaS) ・環境構築不要 ・高い
・借りる度にお金がかさむ
Heroku(PaaS) ・安い
・環境構築不要
・借りる度にお金がかさむ
・30秒タイムアウト辛い
レンタルサーバー(ほぼPaaS) ・安い
・1台でアプリ沢山動かせる
・環境構築ほぼ不要
・root使えない

六. 設計

小規模なサービスなので、ここにはほぼ時間をかけていません。
ワイヤーフレームなどは作らず、実際に画面をコーディングしてレイアウトを決めました。
DB設計もパパッと考えて終わり。
開発の中で必要になったときに都度、テーブルやカラム・画面を増やしていきました。
サービスによっては色々な機能を思いつくと思いますが、まずはスモールスタートでリリースすることをゴールにしましょう。YAGNIは正義。

七. 開発

一番時間をかけたのはこの工程です。
他の仕事が並行していたため正確ではありませんが、全体で2〜3週間はかかったと思います。
伝えたい情報がある場合はコメントを書いています。
有料サービスのみ必要になる項目には「☆」を付けています。

1. サービスの機能開発

一. Twitterログイン

ツイ消し職人では、決済完了時とツイート削除完了時に確認メールを送信しています。
そのため、Twitter AppのAdditional permissionsとして、Request email addressにチェックを入れています。

二. ☆決済

Stripe Checkoutは神。
JavaScriptをちょろっと書くだけで決済を提供できます。返金もボタンポチるだけです。
ツイ消し職人はクレジットカード、Google Pay、Apple Payに対応しています。

三. アーカイブアップロード

Twitterが生成したデータを読み取らないといけないので、アーカイブのどのファイルに何の情報があるのかを全て自分で調べました。
そして、ツイートの削除に本当に必要なファイルだけをアップロードさせることで、ファイルサイズを31GB→200MBまで減らすことができました。

アーカイブからはツイートの削除に必要な情報を正規表現で抽出する必要があります。
最初は、JavaScriptを使いフロント側で情報を抽出し、サーバーには最低限のデータだけ送るようにする予定だったのですが、少し時間がかかりそうだったので諦めました。

FileReader.readAsText()に100MBのファイルを食わせるとクラッシュしてしまうことが判明し、ファイルをチャンクして処理する必要が出てきたためです。
コンソールにエラーは出力されず、サイレントでクラッシュするので問題の特定に時間がかかりました。マジでやめてほしい。
サーバ側で抽出処理をやっても特に問題はないので、サーバ側で処理するようにしました。

四. ツイート削除

ノーコメント

五. 非同期処理

アップロードされてそのままツイートの削除を行うと、画面がタイムアウトしてしまいます。
そのため削除処理はLaravelのキューを使って非同期にしています。
失敗時の再実行もできるようになるので便利ですね。

六. メール送信

必ずユーザーに到達するようにSendGridを使っています。
返信や問い合わせを受けるためにはメールサーバーの設定が必要なので注意してください。
サーバーが用意できない場合は、G Suiteなどのホスティングサービスを利用しましょう。

七. ログ出力 + Slack通知

本番での例外発生時にはSlackにスタックトレースを飛ばすようにしています。
他にも、ツイート削除処理成功時など、正常系でも重要なものはSlackに通知を飛ばしてます。

ログは、出せる項目をなるべく出すようにしています。
Laravelのログ出力について記事書いてるので興味あったら読んでください↓
【Laravel】ログのフォーマットを変更してIPアドレスやユーザー名などを出力する

2. リリース準備

一. LP(トップページ)作成

ユーザーに効果的に訴求できる文言を考える必要があります。
デザイナーの人は腕の見せどころだと思います。
文字や画像・アニメーションを使っていい感じのレイアウトにしましょう。

ペライチなどのツールを使っても良いと思います。
コンバージョンに直結するので、一番力を入れるべき部分です。外注も考えましょう。

二. 利用規約・プライバシーポリシーの制定

この本が大変参考になりました。コピペできるひな形データも付いてくるのでオススメです。

三. ☆特定商取引法に基づく表示の作成

同上。
有料サービスの場合は必須です。
本名や住所、電話番号を晒さないといけないので、ここが一番の難関ではないでしょうか。
私の場合、IP電話アプリ(SMARTalk)を使い050から始まる電話番号を載せています。

四. Googleアナリティクス・Search Console設定

ノーコメント

五. meta description設定

Googleの検索結果でタイトルとともに出るやつです。
meta keywordは不要です。

六. ファビコン設定

GIMPで作りました。
サービスのロゴがある場合はファビコンにも活かせます。

七. OGP設定

GIMPで作りました。
OGP画像を動的に生成するサービスでは、トップページ用の画像を同じ方法で作るのも良いかも。
CTRに直結するので、ここも外注を検討しましょう。

八. サイトマップ設定

Search Consoleで送信します。
sitemap.xml Editorを使うと簡単に作成できます。
サイトが新しく、外部からのリンクが少ない場合はあったほうが良いみたいです。

九. SNSシェアボタンの設置

ツイ消し職人 - twikeshi.net.png
↑こういうのです。
ユーザーに拡散してもらえる仕組みを作っておくことは重要です。
ちなみにツイ消し職人は全く拡散されていません。悲しい。

十. お問い合わせフォームの設置

自分で作るのがめんどくさい場合は、Googleフォームformrunなどを活用しましょう。
お問い合わせフォームの代わりに、チャットサポートツールを入れるのもオススメです。

3. リリース

一. デプロイ

Laravel + レンタルサーバーの場合はgit pullすればほぼ終わりです。
あとは.env書いてマイグレーションしてキャッシュ系のコマンドを叩くだけです。
もちろん、GitHub ActionsなどのCIを設定するのも良いと思います。

私の場合は、以下のようなデプロイスクリプトを用意しています。

deploy.sh
#!/bin/sh

git pull

composer install --optimize-autoloader --no-dev

php artisan config:cache

php artisan view:cache

php artisan route:cache

二. テスト

本番環境で全ての機能がうまく動くことを確認しました。
中規模〜大規模サービスの場合は、検証環境の用意とテスト自動化がされていないと運用がしんどくなります。

八. リリース後

1. 知り合い・友人への拡散

LINE, Twitter, Facebookなどで拡散して使ってもらいましょう。

2. プレスリリースを出す

お金があればPR TIMESなどの有名サイトに出すのがオススメです。
(もしくは、法人ならスタートアップチャレンジの条件を満たすと無料になります)

私はお金がなかったので、valuepressのフリープランで配信しました。
3,200件を超えるツイートを一括削除できるツイ消しサービス「ツイ消し職人」を提供開始

3. アプリ紹介サイトに登録

私の場合、AnyMakemakepostEggineerApplishowを活用しています。

4. 新聞の広告枠に出稿する

リリース直後にスポーツ新聞の方から電話があり、新聞とサイトに広告を載せないかと打診がありました。
条件が合わなかったためお断りしましたが、人によっては選択肢になり得ると思います。

5. アフィリエイト広告に出稿する

現在検討中です。
お金がある場合は、A8.netなどの大手ASPを使うのが良いと思います。
もしもアフィリエイトマネートラックならば初期費用0円・月額費用0円で始められるようです。

6. SNS広告に出稿する

現在検討中です。
私の場合はTwitterユーザーをターゲットにしたサービスなので、Twitter広告と相性が良いです。
とりあえず試してみて、どれくらい成果が出るかチェックしてみようと思います。

7. 保守開発

本番環境でのエラーを監視し、新しく発現したバグがあれば修正しましょう。
手元で再現しないエラーは...ユーザー問い合わせを待つしか無い。。

もちろん、機能追加などサービス改善のための開発は怠らないようにしましょう。
大幅リニューアルや作り直しなどの選択肢もあります。

8. ブログなどでの発信

この記事のことですね。
Qiita, Crieit, Note, ブログなどの選択肢があります。
サービスを知ってもらうだけでなく、転職活動などでも役に立ちます。

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

phpについて(初心者)その1

はじめに

こちらではphpについて問合せがきた内容について書いていきたいと思います。
(適切な題名が思い浮かばなかったためその1といたします)
はじめはどう書いたらいいのかわからなく、初歩的な内容だとそもそもググっても出てこず、挫折することもあると思います。
こちらではその内容を解決する手助けができればと思います。

コードは問合せ者からいただいたものを部分的に抽出し、書き換えたものです。
自分のコードと置き換えつつ解決できればとおもいます。
またこれは1つの案にすぎません。
こんなやり方もあるんだなと参考程度になればと思います。
また、もっとスマートな方法があるとは思いますが、ご容赦いただければと思います。

環境

CentOS
Apache
Vagrant
※こちらは筆者の環境であり問合せ者の環境ではありません

内容

問合せ:htmlから取得した内容をフィールドに反映したいが一つの
フィールドに複数の要素を反映したい。

いただいたコード(実際はもっとながいですがこん今回は一部抜粋)

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>index</title>
  <link rel="stylesheet" href="css/style.css">
</head>
<body>     
  <header>
    <h1>インプット</h1> 
  <header>  
  <form action="test.php" method="post">
    <table border="3">
      <tbody>
        <tr>  
          <td align="left" valign="top" class="title">name</td>
          <td><input type="text" name="comment"></td>
        </tr>
        <tr>
          <td align="left" valign="top" class="title">checkbox</td>
          <td>
            <input name="checkbox" type="hidden" value="">
            <input type="checkbox" name="checkbox[]" value="checkbox1">checkbox1</input>
            <br>
            <input type="checkbox" name="checkbox[]" value="checkbox2">checkbox2</input>
          </td>
        </tr>
      </tbody>
    </table>
    <input type="submit" value="送信" class="button">
  </form>
</body>
</html>
test.php
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
  <header>
    <h1>確認画面</h1>
  </header>
  <table border="2">
    <?php

    $Array = $_POST;


    foreach ($Array as $key => $value) {
      if ($key == 'comment') {
        echo "<tr>";
        echo "<td class='title'>" . $key . "</td>";
        echo "<td>" . $value . "</td>";
        echo "</tr>";
      }
      if ($key == 'checkbox') {
        echo "<tr>";
        echo "<td class='title'>".$key."</td>";
        for ($i = 0; $i < count($value);++$i) {  
          echo "<td>".$value[$i]."</td>";
        } 
      }
    }
    ?>
   </table>
</body>
</html>

画面1

スクリーンショット 2020-05-17 4.04.54.png

遷移後画面

スクリーンショット 2020-05-17 4.07.37.png

期待結果
スクリーンショット 2020-05-17 4.05.22.png

フィールドが新しく作られるようになっています。
なのでコードの内容を噛み砕いていきたいお思います。

test.php
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
  <header>
    <h1>確認画面</h1>
  </header>
  <table border="2">
    <?php
  #入力内容の取得
    $Array = $_POST;

    foreach ($Array as $key => $value) {
      #コメントレコードの作成
      if ($key == 'comment') {
        echo "<tr>";
        echo "<td class='title'>" . $key . "</td>";
        echo "<td>" . $value . "</td>";
        echo "</tr>";
      }
      #チェックボックスレコードの作成
      if ($key == 'checkbox') {
        echo "<tr>";
     #kayフィールドの作成
        echo "<td class='title'>".$key."</td>";
     #valueフィールドの作成
          echo "<td>"/*ここに書く*/"</td>";
        }      
      }
    }
    ?>
   </table>
</body>
</html>

test.php
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
  <header>
    <h1>確認画面</h1>
  </header>
  <table border="2">
    <?php

    $Array = $_POST;

    foreach ($Array as $key => $value) {
      if ($key == 'comment') {
        echo "<tr>";
        echo "<td class='title'>" . $key . "</td>";
        echo "<td>" . $value . "</td>";
        echo "</tr>";
      }
      if ($key == 'checkbox') {
        echo "<tr>";
        echo "<td class='title'>".$key."</td>";
        echo "<td>";
        #for文の配置変更
        for ($i = 0; $i < count($value);++$i) {
        #ここから加えたコード
       if($i==0){  
            echo $value[$i]; 
          }else{
            echo ",".$value[$i];
          }
     #ここまで 
        } 
        echo "</td>";
      }
    }
    ?>
  </table>
</body>
</html>

まとめ

いかがだったでしょうか、なるべく変更を加えることなく期待結果になるようにしてみました。
解説が下手でわかりにくかもしれませんが少しでも助けになればと思います。

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

VSCode でPHPをステップ実行するまでの3つの手順 (ローカル版 + VM環境版)

更新
2020/05/17 launch.json に pathMappings の例を追記


  • 今日も明日も明後日も VSCode でしょう?

ゴール

  • 以下の場合でステップ実行できること
    • デバッグ対象がローカル -- ローカルに PHP, Web サーバ, ソースコード, Vscode がある場合
    • デバッグ対象がVM上 -- ゲストOSに PHP, Web サーバ, ソースコード ホストOSに VScode がある場合

debug.gif

書かないこと

  • PHPと Web サーバのインストール手順

準備

  1. 拡張 PHP Debug をインストール

    shift + ctrl + xphp debug を入力

  2. デバッグ構成ファイルを作る

    /.vscode/launch.json
    {
        "version": "0.2.0",
        "configurations": [
            {
                "name": "Listen for XDebug PHP",
                "type": "php",
                "request": "launch",
                "port": 9000,
    
                // 以下、デバッグ対象がVM上にある場合のみ書く
                "pathMappings": {
                    // ゲストOSのソースの場所 : VSCode のあるホストOSから見えるゲストOS上のソースの場所
                    // e.g. いずれかひとつでOK
                    // "C:/app/src/wwwroot" : "//guest-hostname/wwwroot"
                    // "C:/app/src/wwwroot" : "//192.168.100.101/wwwroot"
                    // "C:/app/src/wwwroot" : "${workspaceRoot}"
                }
            }
        ]
    }
    
  3. Xdebug をインストール

    ⅰ. php -i の結果 (全部)https://xdebug.org/wizard に張り付ける

    ⅱ. DLLをダウンロード

    ⅲ. php.ini と同じ階層に ext フォルダを作りそこにダウンロードした DLL を置く ※ php.ini の場所は上述のコマンドで出力されている

    ⅳ. Xdebug を設定

    /php.ini
    ; ローカルのみで環境をつくってる場合
    zend_extension = php_xdebug-2.9.5-7.4-vc15-x86_64.dll
    xdebug.remote_enable = 1
    xdebug.remote_autostart = 1
    xdebug.remote_host=localhost
    
    ; - - -
    
    ; デバッグ対象がVM上にある場合
    zend_extension = php_xdebug-2.9.5-7.4-vc15-x86_64.dll
    xdebug.remote_enable = 1
    xdebug.remote_autostart = 1
    ; ゲストOSから見たホストOSのIP
    xdebug.remote_host=192.168.100.101
    
    

    ⅴ. Web サーバを再起動

  4. (デバッグ対象がVM上にある場合のみ) 公開ルートをホストと共有

C:/app/src/wwwroot を共有フォルダに設定する

ステップ実行

  1. 「ブレークモード」になることを確認

余談ですがブレークモードで ctrl + alt + w して w だけ離して a で選択してる式をウォッチ式に追加できますね。この辺り Visual Studio のショートカット体系に寄せてくれていて、ありがたいです?

参考

Documentation - all settings: https://xdebug.org/docs/all_settings

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

AWSのELB(Elastic Load Balancing) 構成時に Auth0 認証の例外 (Invalid state) に対処する

経緯

  1. ローカル環境 (ロードバランサー無し) ではうまく認証できる。
  2. AWS上 (ロードバランサーあり) だと Invalid state でエラーになる。

    /Auth0.php
    throw new CoreException('Invalid state');
    

    https://github.com/auth0/auth0-PHP/blob/660163b31beae4c550db68022c568b41df75d8e9/src/Auth0.php#L518

  3. エラーの原因を調べる。どうやら xxx.auth0.com 側の認証処理後に callback url に乗せて Get パラメータで戻される state の値とサーバの state の値に相違がある...。

結論

EC2 > ロードバランシング > ターゲットグループ > 属性の編集 > 「維持設定 有効化: on」 にする。

image.png

注: 根本解決ではないが、ひとまずこの対処でいくことにした。
一定負荷を超えた場合に備えてスケーラブルなサービスを提供している場合、この方法ではのちのち問題になるので Amazon ElastiCacheを使うのが定石らしい。

余談

んなことで2時間ちかく時間が...けどこれロードバランサー使うときの常識なんだろうな?
ちゃんと理解しておかないと他のトラブル時に対処できない、まずいな。

参考

スティッキーセッションというらしい。
https://hack-le.com/sticky-session/

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

VSCode でPHPをステップ実行するまでの3つの手順 (ローカル版 + VM環境版)

追記: (1)参考に XdebugDocumentation - all settings のURLを追記した。(2) XDebug を Xdebug に表記修正。

今日も明日も明後日も VSCode でしょう?

ゴール

  • 以下の場合でステップ実行できること
    • デバッグ対象がローカル -- ローカルに PHP, Web サーバ, Vscode がある場合
    • デバッグ対象がVM上 -- ゲストOSに PHP, Web サーバ ホストOSに VScode がある場合

debug.gif

書かないこと

  • PHPと Web サーバのインストール手順

準備

  1. 拡張 PHP Debug をインストール

    shift + ctrl + xphp debug を入力

  2. デバッグ構成ファイルを作る

    /.vscode/launch.json
    {
        "version": "0.2.0",
        "configurations": [
            {
                "name": "Listen for XDebug PHP",
                "type": "php",
                "request": "launch",
                "port": 9000,
                // 以下、デバッグ対象がVM上にある場合のみ書く
                "pathMappings": {
                    // ゲストOSのソースの場所 : VSCodeが認識するソースの場所
                    "C:/FURUNOSYSTEMS/UNIFAS/WWWROOT" : "//Win-5vkkk6q23vh/wwwroot"
                }
            }
        ]
    }
    
  3. Xdebug をインストール

    ⅰ. php -i の結果 (全部)https://xdebug.org/wizard に張り付ける

    ⅱ. DLLをダウンロード

    ⅲ. php.ini と同じ階層に ext フォルダを作りそこにダウンロードした DLL を置く ※ php.ini の場所は上述のコマンドで出力されている

    ⅳ. Xdebug を設定

    /php.ini
    ; ローカルのみで環境をつくってる場合
    zend_extension = php_xdebug-2.9.5-7.4-vc15-x86_64.dll
    xdebug.remote_enable = 1
    xdebug.remote_autostart = 1
    xdebug.remote_host=localhost
    
    ; - - -
    
    ; デバッグ対象がVM上にある場合
    zend_extension = php_xdebug-2.9.5-7.4-vc15-x86_64.dll
    xdebug.remote_enable = 1
    xdebug.remote_autostart = 1
    ; ゲストOSから見たホストOSのIP
    xdebug.remote_host=192.168.100.xxx
    
    

    ⅴ. Web サーバを再起動

ステップ実行

  1. 「ブレークモード」になることを確認

余談ですがブレークモードで ctrl + alt + w して w だけ離して a で選択してる式をウォッチ式に追加できますね。この辺り Visual Studio のショートカット体系に寄せてくれていて、ありがたいです?

参考

Documentation - all settings: https://xdebug.org/docs/all_settings

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