20190711のPHPに関する記事は16件です。

PHP で Google reCAPTCHA v3 付きのお問い合わせフォームを作る (2)

PHP で Google reCAPTCHA v3 付きのお問い合わせフォームを作る (1) の続きです。
前回までで、 Google reCAPTCHA v3 スコア算出とそれによる条件分岐を作成しました。
ここからは Swift Mailer でのメール送信処理を書いていきます。

メール送信処理

日本語メールを扱うための設定

Swift Mailer の公式ドキュメントにもある、日本語メールを扱うための設定を追加します。
Using Swift Mailer for Japanese Emails

send.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use ReCaptcha\ReCaptcha as ReCaptcha;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  $response = (new ReCaptcha('<シークレット キー>'))
                  ->setExpectedHostname($_SERVER['SERVER_NAME'])
                  ->setExpectedAction('inquiry')
                  ->setScoreThreshold(0.5)
                  ->verify($_POST['token'], $_SERVER['REMOTE_ADDR']);
  $challenge_result = $response->toArray();

  if ($challenge_result['success']) {
    \Swift::init(function () {
    \Swift_DependencyContainer::getInstance()
      ->register('mime.qpheaderencoder')
      ->asAliasOf('mime.base64headerencoder');

      \Swift_Preferences::getInstance()->setCharset('iso-2022-jp');
    });
  }
}

SMTP 認証情報を設定する

以下のコードは Gmail の SMTP サーバを使用した例ですが、お使いになるものに置き換えてください。

send.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use ReCaptcha\ReCaptcha as ReCaptcha;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  $response = (new ReCaptcha('<シークレット キー>'))
                  ->setExpectedHostname($_SERVER['SERVER_NAME'])
                  ->setExpectedAction('inquiry')
                  ->setScoreThreshold(0.5)
                  ->verify($_POST['token'], $_SERVER['REMOTE_ADDR']);
  $challenge_result = $response->toArray();

  if ($challenge_result['success']) {
    \Swift::init(function () {
    \Swift_DependencyContainer::getInstance()
      ->register('mime.qpheaderencoder')
      ->asAliasOf('mime.base64headerencoder');

      \Swift_Preferences::getInstance()->setCharset('iso-2022-jp');
    });

    $transport = (new \Swift_SmtpTransport('smtp.gmail.com', 587, 'tls'))
        ->setUsername('username@gmail.com')
        ->setPassword('****************');
  }
}

送信先・件名・本文を設定する

バリデーション処理などは一旦おいておいて、とりあえず送信テストをするための記述を行います。
setTo() メソッドは例外が起きる可能性があるので、 try-catch で例外処理をしておきます。

Sending Emails in Batch

If you add recipients automatically based on a data source that may contain invalid email addresses, you can prevent possible exceptions by validating the addresses using Egulias\EmailValidator\EmailValidator (a dependency that is installed with Swift Mailer) and only adding addresses that validate. Another way would be to wrap your setTo(), setCc() and setBcc() calls in a try-catch block and handle the Swift_RfcComplianceException in the catch block.

info@example.com の部分は送信したいメールアドレスに置き換えてください。

send.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use ReCaptcha\ReCaptcha as ReCaptcha;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  $response = (new ReCaptcha('<シークレット キー>'))
                  ->setExpectedHostname($_SERVER['SERVER_NAME'])
                  ->setExpectedAction('inquiry')
                  ->setScoreThreshold(0.5)
                  ->verify($_POST['token'], $_SERVER['REMOTE_ADDR']);
  $challenge_result = $response->toArray();

  if ($challenge_result['success']) {
    \Swift::init(function () {
    \Swift_DependencyContainer::getInstance()
      ->register('mime.qpheaderencoder')
      ->asAliasOf('mime.base64headerencoder');

      \Swift_Preferences::getInstance()->setCharset('iso-2022-jp');
    });

    $transport = (new \Swift_SmtpTransport('smtp.gmail.com', 587, 'tls'))
        ->setUsername('username@gmail.com')
        ->setPassword('****************');

    $mailer = new \Swift_Mailer($transport);

    $message = (new \Swift_Message('お問い合わせフォームからお問い合わせがありました'))
        ->setFrom([filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL) => filter_input(INPUT_POST, 'name', FILTER_SANITIZE_FULL_SPECIAL_CHARS)])
        ->setBody(
          'お名前: ' . filter_input(INPUT_POST, 'name', FILTER_SANITIZE_FULL_SPECIAL_CHARS) . PHP_EOL .
          'メールアドレス: ' . filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL) . PHP_EOL .
          'お問い合わせ内容: ' . filter_input(INPUT_POST, 'contents', FILTER_SANITIZE_FULL_SPECIAL_CHARS));

    try {
      $message->setTo(['info@example.com']);
    } catch (\Swift_RfcComplianceException $e) {
      echo '不正なメールアドレスです。';
    }
  }
}

送信処理

send.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use ReCaptcha\ReCaptcha as ReCaptcha;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  $response = (new ReCaptcha('<シークレット キー>'))
                  ->setExpectedHostname($_SERVER['SERVER_NAME'])
                  ->setExpectedAction('inquiry')
                  ->setScoreThreshold(0.5)
                  ->verify($_POST['token'], $_SERVER['REMOTE_ADDR']);
  $challenge_result = $response->toArray();

  if ($challenge_result['success']) {
    \Swift::init(function () {
    \Swift_DependencyContainer::getInstance()
      ->register('mime.qpheaderencoder')
      ->asAliasOf('mime.base64headerencoder');

      \Swift_Preferences::getInstance()->setCharset('iso-2022-jp');
    });

    $transport = (new \Swift_SmtpTransport('smtp.gmail.com', 587, 'tls'))
        ->setUsername('username@gmail.com')
        ->setPassword('****************');

    $mailer = new \Swift_Mailer($transport);

    $message = (new \Swift_Message('お問い合わせフォームからお問い合わせがありました'))
        ->setFrom([filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL) => filter_input(INPUT_POST, 'name', FILTER_SANITIZE_FULL_SPECIAL_CHARS)])
        ->setBody(
          'お名前: ' . filter_input(INPUT_POST, 'name', FILTER_SANITIZE_FULL_SPECIAL_CHARS) . PHP_EOL .
          'メールアドレス: ' . filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL) . PHP_EOL .
          'お問い合わせ内容: ' . filter_input(INPUT_POST, 'contents', FILTER_SANITIZE_FULL_SPECIAL_CHARS));

    try {
      $message->setTo(['info@example.com']);
    } catch (\Swift_RfcComplianceException $e) {
      echo '不正なメールアドレスです。';
    }

    try {
      $send_result = $mailer->send($message);
    } catch (\Swift_TransportException $e) {
      echo 'メール送信エラーです。';
    }

    if ($send_result) {
      echo 'メールを送信しました。';
    } else {
      echo 'メール送信に失敗しました。';
    }
  }
}

とりあえずここまででメールの送信テストが可能な状態になりました。
すべての入力欄を埋めて 送信する ボタンを押して試してみてください。

このままでは実用に耐えないので、

  • バリデーション処理
  • エラーが発生したときにお問い合わせフォームに戻し、エラーの内容を表示する
  • メール本文をテンプレート化して編集しやすくする

など、細かな改善を次回以降で行っていきます。

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

WordPressのREST APIで投稿をslugから取得する方法

こんにちは、プログラミングスクールのレビューサイト「スクールレポート」を運営しているアカネヤ(@ToshioAkaneya)です。

WordPressのREST APIで投稿をslugから取得する方法

https://example.com/wp-json/wp/v2/posts/?slug=<slug>
が使えます。

下のブログはWordPressで構築されたブログの1記事です。
https://toshioakaneya.com/school-report-release/

WordPressのREST APIによりこの投稿のslugから記事の情報を取得するには、以下のパスを使用します。
https://toshioakaneya.com/wp-json/wp/v2/posts/?slug=school-report-release

終わりに

Ruby on RailsとVueで作成したプログラミングスクールのレビューサイトを運営しています。良ければご覧ください。https://school-report.com/

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

UdemyのPHP+MySQL(Byたにぐちまこと)をまとめてみた

はじめに

Udemyの講座を受けながら、まとめた自分なりのアウトプットです!

同じ講座を受ける際には参考にしてみてください!!!!!!

セクション1はセットアップのため飛ばして、2からになっています。

セクション2:PHPの基本を学ぼう

・HTMLタグのname属性がformタグで送信する際の、識別子になる

・inputタグは閉じタグが必要ない=単独タグ

・input type = submitタグのvalueタグはボタンの表面に表示する文字列

・htmlspecialchars($REQUEST[my_name],ENT_QUOTES)
 →$_REQUESTmy_nameをENT_QUOTESの型にエスケープさせるメソッド
 ※特殊な場合以外は基本的にENT
QUOTES

・$_REQUESTはPOSTでもGETでもどちらでも受け取ることができる
 →GETかPOSTどちらが受け取れてるのかわからないため、基本的にわかっている場合は指定したほうがいい

・ラジオボタンはvalue属性が送信される(textfieldも一緒でvalue属性を送っている)
 フォームで送られるものはvalue属性に設定されているものが送信される
 チェックボックス、セレクトタグ、etc

・チェックボックスで複数の値を送信する場合はname属性にブレケッツ[]をつける必要がある
 →配列の形式で送信されるため
 →そのため受け取る時は配列で受け取り、foreach($_POST['reserve'] as $reserve)などで取り出す!

・変数はその画面で終わる
 セッションは画面間を移動してもブラウザを閉じるまでは保存されている
 (サーバーにsession IDが保存されていて
 、ブラウザ側のクッキーにsession IDを持たせてそれと付き合わせて照合する)
 クッキーは画面を閉じても有効期限までは保存されている(ブラウザ側に保存 パスワード等は入れちゃダメ!)
 →保存期間がそれぞれ違うから区別して使う!

セクション3 データベースの基本を学ぼう

・UPDATEとDELETEのSQLはWHERE句で指定しないと全ての値が対象になるから気をつけて実行する必要がある

・PHPMyadminでは主キーの設定が上手くいかないことがある
→SQLで実行する!

・PHPMyadminはもともと英語のシステムを日本語で割り当ててるから文法とかおかしいのがたまにある
ex)カラムの追加

・主キーは一意の値のカラムになる(idとか)

・AUTO_INCREAMENTした状態で何か値を削除したらそこの採番は永久欠番になる

・基本DBで一度削除した値と同じ採番は実装しない!

・WHEREのあいまい検索はLIKE %~% で行う

・TIMESTAMPという型はデータの変更時間を自動的に入れてくれる

・LEFT JOIN 
 SELECT item_name,SUM(COUNT) FROM my_items LEFT JOIN carts ON my_item.id=carts.item_id GROUP BY carts.item_id

 my_itemsに属する形でcartsに結合させるからcartsにないものもmy_itemsにあればそれに紐づいて表示される
 ON以降にテーブルを繋げる条件を記述

・DISTICNTで値を重複させず取得できる

・BETWEEN 50 AND 149
 でWHERE price > 50 AND price < 150と同じ

・WHERE id IN(1,3)
複数の条件を記述できる

・LIMIT 2,3(2個目から3つ取得)
第一パラメータがどこから第二パラメータで何件かを取得するという記述

・半角空白を書くとそのテーブル名などのオブジェクトをエイリアスで記述できる
 SELECT item_name,SUM(COUNT) FROM my_items i LEFT JOIN
carts ON i .id=carts.item_id GROUP BY carts.item_id

・SQLが集まったものとしてエクスポートでバックアップを取れる

・復元はDBを作成したあとにインポートでバックアップしたSQLを取り込む

セクション4: PHP+MySQL(MariaDB)を組み合わせて、Webシステムを作ろう

・$db = new PDO('mysql:dbname=mydb;host=127.0.0.1';charset=utf8,'root','root');
でアクセス host=127.0.0.1は自分自身を表すIPアドレス

・exec()メソッドは影響を与えた行の数を返す
 query()メソッドはSQLの結果の値を返す
execute()メソッドはプリペアードステートメントを実行する

・queryはオブジェクトなのでそこから値を取得するにはfetch()する
 $record = $records -> fetch();の$recordは連想配列になっている

・$statement = $db->prepare('INSERT INTO memos SET memo = ?,created_at = NOW()');
$statement -> execute(array($_POST['memo'])));
で?の部分にexecuteで値を入れる

・bindparamで複数の?にSQLを代入できる

・Session以外で次のページに値を受け渡す方法として、
 inputタグのhidden属性を使ってPOSTでそのvalueを渡してやるといった方法もある

・PHPではCRUDはデータベースに直接SQLを発行して行う

セクション5: 「Twitter風ひとこと掲示板」を作成しよう

・varchar型は文字数を制限したtext型のこと

・header('Location:check.php')でcheck.phpにリダイレクトできる
 その後路にはexitが必要

・formタグでファイルをアップロードする場合は

が決まり文句

・$image = date('YmdHis'). $_FILES['image']['name'];
でファイル名がアップロードされた時に日付を付加してファイル名が重複するのを防ぐ

・ move_uploaded_file($FILE['image']['tmp_name'],'../member_picture/' . $image);
$_FILEがinput type = fileであげたものが入っているグローバル変数
['image']['tmp_name']は一時的に上がっている場所
move
uploaded_file(ファイル、ファイルの移動先../member_picture/' . $image)

・substr($fileName, -3)でファイルの拡張子を得る(後ろから3文字を切り取る)

・セッションは使い終わったらすぐに破棄するようにする
unset($_SESSION['join']);

・textareaのformパーツはvalueタグがなく、その代わりにに囲まれた部分がvalueになる

最後に

Evernoteにまとめたやつを貼り付けただけなので、こっちが元になっています
見づらかったら以下を参考にしてください!
https://www.evernote.com/shard/s625/sh/d81568ef-475c-4c15-b3c6-ae9da198f15c/0573994a5c161bb9cbdb23f85cd37ba9

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

【PHP】公式リファレンスをすんなり読るようになれば、一気に学習が進む説【初心者】

PHP学習を進めるにあたり、よく使う参考資料は何か?と言われたら公式リファレンスが思いつくことだろう。

PHP公式リファレンス

とはいえこのサイト、初心者にとっては中々使いにくい。

初心者に公式リファレンスが使いにくい理由

何故使いにくいのかと言えば、その専門的な言葉使いが原因だ。
公式リファレンスは容赦なく専門用語が飛び出す。まるで少し難しい用語を調べた時に出て来るwikipediaみたいに…。

初心者相手に書くとなればそれ相応の優しい言い回しをする事になるが、公式リファレンスという立場上砕け過ぎた表現は許されない。

なので、結局初心者は他の解りやすい解説のサイトを使って関数の使い方等を学んで…といった事をしてしまう人も多くいると思うのだが、逆にこう考えてみると学習が一気に進むのでは?と思いついた。

「公式リファレンスを見て出て来る専門用語を理解できていれば、PHPの学習効率が高まるうえ用語も覚えられて一石二鳥なのでは?」

公式リファレンスから知らない単語を抽出して語彙力を伸ばそう(提案)

公式リファレンスでも小難しいwikipediaでも中身が解らない理由はただ1つ。
語彙力が無いからだ。
書いてある言葉をちゃんと理解できているのなら、そもそも理解できるのであれば問題になることはない。

逆にいえば公式リファレンスで触れられた言葉をマスターして説明書をみただけで何をどうすればよいのか解る人は、その時点でPHPに関する知識を十分に蓄えた人に自動的になれるとも言えるのではないだろうか?

まとめ

・公式リファレンスになじめる努力をしてみよう。
・"知らない"を"お堅い説明書"から抽出して自身の語彙力を伸ばそう。

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

Laravel で API リソースを使って CSV ダウンロードする

はじめに

Laravel で CSV ダウンロードするときのアーキテクチャはいくつかある。

けど、コントローラからモデルをとって、整形して、CSV にするなら、
標準の JSON API リソースが JSON じゃなくて CSV になるだけでもよいのではないかと思った。
そして意外となかったので、書いてみます。

CSV API リソースを実装する

標準の JSON API リソースの実装を見て、最低限な次の機能を実装しました。

  • CSV だと複数のレコードを扱うように思うので、コレクションを扱う ResourceCollection のみ
  • 整形とダウンロードのレスポンス生成のみ(ほかにも機能があるっぽい)
ResourceCollection.php
<?php

namespace App\Http\Resources\Csv;

use Illuminate\Contracts\Support\Responsable;

abstract class ResourceCollection implements Responsable
{
    protected $collection;

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

    public function toResponse($request)
    {
        return response()->streamDownload(function () {
            $file = new \SplFileObject('php://output', 'wb');

            $header = $this->attributes();
            if (! empty($header)) {
                $file->fputcsv($this->convertCharset($header));
            }

            foreach ($this->toArray() as $fields) {
                $file->fputcsv($this->convertCharset($fields));
            }
        }, $this->fileName());
    }

    private function convertCharset($fields)
    {
        return collect($fields)
            ->map(function ($item) {
                return mb_convert_encoding($item, 'SJIS-win', mb_internal_encoding());
            })
            ->all();
    }

    /**
     *  見出しがあれば返します
     */
    protected function attributes()
    {
        return [];
    }

    /**
     *  整形した配列を返します
     */
    abstract protected function toArray();

    /**
     *  ダウンロードファイルの名前があれば返します
     */
    protected function fileName()
    {
        return 'download.csv';
    }
}

使ってみる

注文情報を CSV に書き出すことを考えてみましょう。

コントローラ

ほとんど JSON のときと同じです。

OrderController.php
<?php

namespace App\Http\Controllers;

use App\Order;
use App\Http\Resources\ReportResource;

class OrderController extends Controller
{
    public function report()
    {
        return new ReportResource(
            Order::query()
                ->where('is_canceled', false)
                ->get()
        );
    }
}

API リソース

JSON のときと同じく、toArray をオーバライドして整形します。
加えて、attributes で見出しを、fileName でダウンロードファイル名を、オーバライドして指定することもできます。

ReportResource.php
<?php

namespace App\Http\Resources;

use App\Http\Resources\Csv\ResourceCollection;

class ReportResource extends ResourceCollection
{
    protected function toArray()
    {
        $this->collection->load(['user', 'products']);

        return $this->collection
            ->map(function ($order) {
                return [
                    $order->id,
                    $order->user->id,
                    $order->user->name,
                    $order->products->sum('price'),
                ];
            })
            ->all();
    }

    protected function attributes()
    {
        return [
            '注文ID',
            '会員ID',
            '会員名',
            '注文金額合計',
        ];
    }

    protected function fileName()
    {
        return '注文一覧.csv';
    }
}

ダウンロード例

注文一覧.csv
注文ID,会員ID,会員名,注文金額合計
1,1,ほげほげ,1000
2,1,ほげほげ,1500
3,2,ふがふが,1000
4,2,ふがふが,2000

おわりに

JSON のときと同じ使い方ができるので、覚えることが少ないし、標準にも近いかもしれない。

すっきり!

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

【PHP7.4】レガシーな仕様はどんどんしまっちゃおうねぇ

恒例の仕様凍結直前駆け込みRFC第二弾。
色々な古い書き方について、PHP7.4でE_DEPRECATEにし、PHP8で削除を目指すRFCが投票中です。

以下はDeprecations for PHP 7.4の日本語訳です。

Deprecations for PHP 7.4

Introduction

このRFCでは、以下に列挙されている機能についてPHP7.4で非推奨とし、PHP8で削除することを提案します。

Proposal

各提案は個別に投票を行い、投票数の2/3+1の賛成で受理されます。
投票開始は2019/07/08、投票終了は2019/07/22です。

The 'real' type

現在のPHPでは、float型にはdoubleとrealという2種類のエイリアスが存在します。
後者は滅多に使用されず、廃止されるべきです。
これは(real)キャストと、is_real関数の両方が含まれます。
settype関数はrealをサポートしていないため影響はありません。

プログラム側の対応は簡単で、全ての(real)キャストを(float)に、is_real関数をis_floatに置き換えるだけです。

Proposal:(real)キャストおよびis_real関数をE_DEPRECATEDにします

2019/07/11時点では賛成24反対6で、おそらく受理されます。

Magic quotes legacy

悪名高いmagic_quotesはPHP5.4で削除され、magic_quotesの値を調べる関数はそれ以来ずっとfalseを返しています。
PHP7にはmagic_quotesに関する機能は全く存在しないため、これらの関数はもはや存在する意義がありません。

Proposal:get_magic_quotes_gpc関数およびget_magic_quotes_runtime関数をE_DEPRECATEDにします。
この変更が影響するのは、もはやサポートされていないPHP5.4以前のレガシーなコードだけです。

2019/07/11時点では賛成34反対0で、おそらく受理されます。

array_key_exists() with objects

array_key_existsは互換性のためにオブジェクトにも対応しています。

01.png

オブジェクトに対するarray_key_existsは問題があります。
プロパティの可視性を無視して直接中身を覗きます。
また、配列のキーとオブジェクトのキーの取り扱いの違いが考慮されていないため、数値キーのプロパティに対して正しくない結果を返す可能性があります。

さらにarray_key_existsがオブジェクトを受け入れるという事実は、ArrayAccessオブジェクトをarray_key_existsで適切に操作できるのだとユーザに誤解させかねません。
これは誤りで、array_key_existsは実際にはArrayAccessをサポートしていません。

Proposal:array_key_existsにオブジェクト渡すとE_DEPRECATEDにします。

2019/07/11時点では賛成32反対0で、おそらく受理されます。

FILTER_SANITIZE_MAGIC_QUOTES

magic_quotesはPHP5.3で廃止され、PHP5.4で削除されました。

Filter関数は、magic_quotesと同じ動作をaddslashesを呼び出すことで再現するサニタイズフィルタを提供しています。
PHP7.3では、magic_quotesという単語から抜け出すため、FILTER_SANITIZE_MAGIC_QUOTESのエイリアスとしてFILTER_SANITIZE_ADD_SLASHESを追加しました。

Proposal:FILTER_SANITIZE_MAGIC_QUOTESをE_DEPRECATEDにします。かわりにFILTER_SANITIZE_ADD_SLASHESを使ってください。

2019/07/11時点では賛成33反対0で、おそらく受理されます。

Reflection export() methods

全てのリフレクションクラスはReflectorインターフェイスを実装しており、__toString()export()という2つのメソッドが存在します。
後者は静的メソッドであり、一見して引数を受け付けません。

しかし実際は、各サブクラスにおいて引数を受け入れるようにオーバーライドされています。
これは本来は互換性のない引数のエラーが発生するはずですが、内部的にエラーを抑制して実装されています。

exportメソッドは、本質的にコンストラクタ+__toStringと同じです。

    // 同じ
    ReflectionFunction::export('foo');
    echo new ReflectionFunction('foo'), "\n";

    // 同じ
    $str = ReflectionFunction::export('foo', true);
    $str = (string) new ReflectionFunction('foo');

exportメソッドはPHPの継承の規則に反していて混乱を招くため、全く不要です。

Proposal:Reflectorインターフェイスと全ての実装クラスからexportsメソッドをE_DEPRECATEDにします。PHP8で実装を削除します。

2019/07/11時点では賛成25反対3で、おそらく受理されます。

mb_strrpos() with encoding as 3rd argument

mb_strrposのドキュメントにはこのようなことが書かれています。

02.png

非推奨と書かれてはいますが、現状特に警告が出たりはしません。
両方の引数を受け入れるため、このパラメータは他のパラメータとは異なる動作をします。
たとえばstrict typesにしても型エラーが出ません。
PHP5.1とPHP7.4の両方をサポートするアプリはほぼ存在しないと思われるので、ここの動作を変更しても、後方互換性に関する重大な問題は発生しません。

Proposal:mb_strrpos関数の第三引数に文字列を渡すとE_DEPRECATEDにします。PHP8で実装を削除します。

2019/07/11時点では賛成32反対0で、おそらく受理されます。

implode() parameter order mix

歴史的な経緯により、implode関数は引数$glue$piecesを、どちらの順番でも受け入れることができます。
join関数も同じです。

Proposal:implode(array, string)の順番での引数をE_DEPRECATEDにします。implode(array)は引き続き許可されます。

2019/07/11時点では賛成27反対5で、おそらく受理されます。

Unbinding $this from non-static closures

現在、$closure->bindTo(null)を用いてクロージャから$thisを削除することができます。
PHP8では非静的メソッドの静的呼び出しが削除されたため、非静的メソッドには常に$thisが存在することが保証されました。
非静的メソッド内で宣言された非静的クロージャに対して、$thisが常に存在するという同様の保証を得たいと思います。
さもなくば、通常のアクセスもしくはクロージャからのアクセスの何れかに対して、不合理が発生するでしょう。

Proposal:クロージャから$thisの削除をE_DEPRECATEDにします。特に非静的メソッド内で宣言された非静的クロージャに対して適用されます。$thisが不要な場合は静的クロージャを使うことで余計なバインドを避けられます。

2019/07/11時点では賛成31反対0で、おそらく受理されます。

hebrevc() function

hebrevc関数は、hebrev関数の結果をnl2brするのと同じです。
これはヘブライ語のテキストを論理順から視覚順に変換する関数です。

視覚的順序付けの使用は、Unicode bidiに対応していない端末など、ごく一部のコンテキストにのみ使用するべきです。
W3Cが表明しているように、視覚的順序付けはHTMLには使用しないでください。
hebrevc関数はこの原則に明らかに反しています。

Proposal:hebrevc関数をE_DEPRECATEDにします。

2019/07/11時点では賛成20反対8で、受理されるか微妙なところです。

convert_cyr_string()

convert_cyr_string関数は、キリル文字のテキストを他のキリル文字セットに変換します。
変換する文字コードは、convert_cyr_string($str, "k", "i")のように理解しがたい1文字で指定しなければなりません。
この関数は、PHPに文字コード変換のための一般的手法が存在していなかった頃の古い関数です。
現在はmb_convert_encodingiconv、あるいはUConverterなどが、この目的のために使用されます。

Proposal:convert_cyr_string関数をE_DEPRECATEDにします。

2019/07/11時点では賛成17反対7で、受理されるか微妙なところです。

money_format()

money_format関数は、ロケールを見て金額をフォーマットします。
これはC言語のstrfmonを使用していますが、これは全てのプラットフォームがサポートしているわけではなく、特にWindowsでは利用できません。
今日ではintlが提供するNumberFormatter::formatCurrencyを使用すべきです。
これはプラットフォームに依存せず、ロケールにも依存しません。
intlではさらに金額フォーマットをパースするNumberFormatter::parseCurrencyも提供しています。

あと、MacOSでのstrfmonの実装を見ていると、どうもこの関数はバッファオーバーランしているように見受けられます。
これはすなわち、この関数が十分なテストを受けていないことを示しています。

Proposal:money_format関数をE_DEPRECATEDにします。

2019/07/11時点では賛成22反対8で、おそらく受理されます。

ezmlm_hash()

ezmlm_hash関数は、EZMLM/QMailメーリングリストが理解できるメールアドレスのハッシュを作成します。
EZMLM/QMailは最終リリースが2007年であり、全くメンテナンスされていないため、この関数を必要とするPHP開発者は限りなく少数です。
この関数は、元々php.netのメーリングリストで使用するために実装されたものです。
必要であればユーザランドで簡単に実装が可能です。

Proposal:ezmlm_hash関数をE_DEPRECATEDにします。

2019/07/11時点では賛成25反対4で、おそらく受理されます。

restore_include_path() function

restore_include_path関数は、本質的にini_restore('include_path')のエイリアスです。
restore_error_handlerrestore_exception_handlerと異なり、この関数はスタック上で動作せず、必ず初期値にリセットされます。

set_error_handler()とrestore_error_handler()をペアで使用して一時的に動作を変更する操作は有用ですが、set_include_path()とrestore_include_path()を同様にペアで使用することは安全ではありません。
従って、この関数はini_restore('include_path')と別に存在する利点はなく、誤った使い方を助長するだけです。

Proposal:restore_include_path関数をE_DEPRECATEDにします。

2019/07/11時点では賛成19反対9で、受理されるか微妙なところです。

allow_url_include

allow_url_includeディレクティブは、requirerequire_onceincludeinclude_onceの各言語構造にURLストリームラッパーの使用を許可します。
このディレクティブはデフォルト無効で、使用するにはallow_url_fopenも有効にする必要があります。

includeするパスに外部入力を使用している場合、このオプションを有効にするとセキュリティ上の問題が発生します。
外部ドメインからPHPファイルをインクルードする仕様そのものがそもそも極めて問題であり、大きなセキュリティリスクが存在するため、廃止されるべきです。

Proposal:allow_url_includeディレクティブが有効である場合E_DEPRECATEDが発生します。

2019/07/11時点では賛成30反対0で、おそらく受理されます。

Backward Incompatible Changes

PHP7.4では、非推奨の警告が表示されます。
PHP8では、動作しなくなります。

Changelog

元々は以下の項目も廃止予定に含まれていましたが、最終的に対象外になりました。

get_called_class。あまり考えられていなかった。
enable_dl。当初の提案が間違っていた。
・INPUT_SESSIONとINPUT_REQUEST。そもそも実装されてなかったので直接削除した
is_writeable。ただの誤字だけど検索しやすいから好ましいとか主張した人がいたので取り下げられた。
apache_request_headers。大きな問題の中のひとつなので、この関数だけ消して終わりではなく大規模な考察が必要。
hebrev。一部の環境ではまだ有用なため。ただしhebrevcは削除する。
enable_argc_argv、と書いてあるんだけど全く出てこないんだけど存在するのこれ?

感想

ほとんどは削除されて当然というか、そもそも全く知らない関数だった。
hebrevcとかconvert_cyr_stringとか聞いたこともないよ。
使ってる人いるんですかね?
あとmagic_quotesは、むしろどうしてPHP7.0で削除されなかったんだと聞きたいくらいですね。
概ね順当に削除されそうでなによりです。

でもReflection::export()は個人的にデバッグとかで時々使っているからちょっと困るかな。
__toString()はなんというか、これを使うのだ、と明示できないからちょっと気持ち悪いというかなんというか。

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

2行以上連続した要素を一つにする【正規表現】

$item = '
<p>&nbsp;</p>
<p>&nbsp;</p>
<img src="test.jp">
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>あかさたなはまやらな</p>
';

$item = preg_replace('/(<p>&nbsp;<\/p>)\n|\r|\r\n/', '<nbsp_plus_break>', $item);
$item = preg_replace('/(<nbsp_plus_break>){1,}/', "<p>&nbsp;</p>\n", $item);

echo $item;
結果
<p>&nbsp;</p>
<img src="test.jp">
<p>&nbsp;</p>
<p>あかさたなはまやらな</p>

一度、別タグに変換
もっといいやり方絶対あるはず

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

Smarty で指定日時からの数日前・数日後などを表示

今更 Smarty ネタですが。

「現在日時」から○日後とか○日前とかの情報は多数あるのですが
指定日時」からの○日後に関する情報がなかったのでメモ。

$target_date = '2019-05-01'

2019年5月1日の10日後
{(($target_date|strtotime)+24*60*60*10)|date_format:'%Y-%m-%d'}
2019年5月1日の10日前
{(($target_date|strtotime)-24*60*60*10)|date_format:'%Y-%m-%d'}
2019年5月1日の1年後
{(($target_date|strtotime)-24*60*60*365)|date_format:'%Y-%m-%d'}

DATE型の$target_dateを一度Unixタイムに変換してから、時間を加(減)算します。
その後、date_format で再度DATE型に変換しています。

ちなみにおなじみの現在日時からの比較は以下

現在日時から比較して明日
{($smarty.now+24*60*60)|date_format:'%Y-%m-%d'}

参考記事

Smartyで数日前/数日後を計算

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

[Laravel]単体テストでログインできない!

LoginControllerでオーバーライドしたauthenticated()のテストをしていました。

Controllers/Auth/LoginController.php
/**
 * @param \Illuminate\Http\Request $request
 * @param mixed $user
 * @return \Illuminate\Http\Response
 */
protected function authenticated(Request $request, $user)
{
    return redirect('users/' . $user->id)->with('my_status', __('You logged in.'));
}

ログインに成功するとユーザーページにリダイレクトして
You logged in.というフラッシュメッセージを表示します。

テストのコード

/tests/Unit/LoginControllerTest.php
/**
 * A basic unit test example.
 *
 * @return void
 */
public function testAuthenticated()
{
    $user = factory(User::class)->create([
        'password' => '1234'
    ]);

    $response = $this->post('login', [
        '_token' => csrf_token(),
        'email' => $user->email,
        'password' => '1234',
        'remember' => 'on'
    ]);

    $response->assertRedirect('users/' . $user->id);
    $this->assertAuthenticatedAs($user);
}

テストを実行

$ vendor/bin/phpunit --filter='LoginControllerTest'
PHPUnit 7.5.13 by Sebastian Bergmann and contributors.

F.                                                                  2 / 2 (100%)

Time: 2.85 seconds, Memory: 26.00 MB

There was 1 failure:

1) Tests\Unit\LoginControllerTest::testAuthenticated
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'http://localhost/users/1'
+'http://localhost'

/home/user/php/app/lara58/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:224
/home/user/php/app/lara58/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:169
/home/user/php/app/lara58/tests/Unit/LoginControllerTest.php:32

FAILURES!
Tests: 2, Assertions: 4, Failures: 1.

http://localhost/users/1にリダイレクトしていないのでログイン失敗です。

解決方法

/tests/Unit/LoginControllerTest.php
$user = factory(User::class)->create([
    'password' => bcrypt('1234')
]);
$ vendor/bin/phpunit --filter='LoginControllerTest'
PHPUnit 7.5.13 by Sebastian Bergmann and contributors.

..                                                                  2 / 2 (100%)

Time: 2.68 seconds, Memory: 26.00 MB

OK (2 tests, 7 assertions)

成功しました。

他のテストでactingAs()メソッドを使った時はbcrypt()を使わなくてもできたのですが、ログインする時はbcrypt()が必要なようです。

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

【Laravel】updated_atカラムがないテーブルをEloquentモデルを用いて更新するときの注意点

備忘録として残します。

Eloquentモデルを用いて更新をかけるとき下記のような記述をすると思います。
HogeTable::find($id)->fill($input)->save();
このときLaravelはEloquentモデルで更新しようとしたとき、暗黙的にupdated_atというカラムも同時に更新しようとします。

しかし、updated_atというカラムがない場合は、エラーをはくので、この暗黙的にタイムスタンプをする機能をオフにする方法が下記になります。

■やり方

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class HogeTable extends Model
{
    public $timestamps = false; // この行をEloquentモデルに追記
    protected $table = 'hoge';
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

composerでautoloadのclassmapがうまくいかない時のメモ

composerでライブラリ管理をしている場合に、
composer installした際にautoloadのclassmapがうまく生成されず、
クラスがうまく読み込めていないため
class not foundになってしまうことがあった。

そんな時に試してみたメモ

clear-cache

php composer.phar clear-cache 

composerのライブラリはローカルに同バージョンのキャッシュがされているとダウンロードしないので、
一度キャッシュを削除してみる。
その後再度composer installする

dump-autoload

php composer.phar dump-autoload 

autoloadのclassmapを再生成するコマンド。

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

test

  • test
  • test
    • test
    • test

テスト

test

test.php
test
test

test

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

php-master-changes 2019-07-10

今日は sodium 経由で argon2 のパスワードハッシュを使う際の実装修正と libargon2 利用版との互換性向上、ドキュメントの更新、テストの修正、エンジンのリファクタリング、string と resource への参照についての循環参照 GC コストの低減、配列以外の値の変数を配列としてアクセスした際に Notice を吐くようにする修正、run-tests.php で 7.3 系でも CONFLICTS セクションを認識できるようにする修正、JIT のバグ修正、セグメントを HugePage 境界へ配置するのを x86 でのみやるようにする修正があった!

2019-07-10

sgolemon: Relax argon2 mem_cost down to 64k, bump time_cost to 4

petk: Use e.g. instead of less common f.e. [ci skip]

remicollet: improve libargon2/libsodium compatibility

nikic: Avoid invalid array access in fcgi.inc

nikic: Clean up DateTimeZone::getLocation() test

nikic: Fix iteration limits in SXE test

nikic: Simplify two unserialize() tests

nikic: Rename support_strings to !is_list

dstogov: Reduce cost for references to strings and resources

dstogov: Reduce cost for GC references to strings and resources in JIT

nikic: Fix invalid array access in mysqli_fork test

nikic: Throw notice on array access on illegal type

nikic: Implement array access notice in JIT

nikic: Additional fix for mysqli_fork test

nikic: Fix file collisions in phar tests

nikic: Add CONFLICTS to recognized sections

nikic: Read from original address

paxal: Align segments on huge page boundary only for x86

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

【3行クイズ】クラス定義やrequire関数で正常かエラーかよく分からなくなるかもしれないクイズ

はじめに

5つのソースコード(Q01~05)が、○(正常終了)か× (エラー)か答えてください。回答はページ末尾にあります。なお、動作確認したPHPのバージョンは7.2.17です。

※base.phpは共通して下記のソースになります。

base.php
<?php
class base{}

それでは、問題です。

Q.01

q01.php
<?php
new hoge();
class hoge extends base{}
class base{}

Q.02

q02.php
<?php
new hoge();
class base{}
class hoge extends base{}

Q.03

q03.php
<?php
new hoge();
require("base.php");
class hoge extends base{}

Q.04

q04.php
<?php
require("base.php");
new hoge();
class hoge extends base{}

Q.05

q05.php
require("base.php");
class hoge extends base{}
new hoge();

(回答の前に)結論

「ロード(includeやrequire) → 親クラス定義、子クラス定義(extendsやtrait) → インスタンス化 」という順番を守っていれば何の問題にはなりません。とある理由でバラしていたところ当たり前なことがよく分からなくなりました。私と同じように「わけが解らなくなる人」がいるのではないかと思いクイズにしてみました。

みなさんは、一瞬で答えられたでしょうか?

回答

  1. × エラー
  2. ○ 正常終了
  3. × エラー
  4. × エラー
  5. ○ 正常終了

参考リンク

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

【web開発】動的に用意した要素にイベントを追加

躓き

<a class="anker" href="#">anker0</a>

こちらはHTMLで処理前に用意した要素・

let string = array("anker1","anker2","anker3");
for (let i=0; i<3; i++){
  $('.class').append('<li><a class="anker" href="#">'+string[i]+'</a></li>');
}

こちらはJavaScriptで動的に用意した要素。

$('.class').on("click", function(e){ /* 処理 */});

上で用意した要素全てにclickイベントを追加したつもりが、JavaScriptで動的に用意した要素に対してはclickイベントは機能しなかった。

解決策

$(document).on("click", ".anker", function(e){ /* 処理 */ });

こうすることで、動的に用意した要素にもclickイベントを追加することができた。

onメソッドについて

基本形

.on( events [, selector ] [, data ], handler )
引数 データ型 説明
events String 半角スペース区切りでイベントタイプ("click"など)とoptional namespaces("keydown.myPlugin"など)を単数もしくは複数設定可能
selector String 選択された要素の子孫要素に対してイベントデリゲートできる
data Anything イベントハンドラに渡したいデータをハッシュで指定できる。
handler Function eventsが起きたタイミングで実行される機能

events

基本的な使い方は省略するが、連想配列を使用しイベント毎に機能を付与できるのは便利。

$('.foo').on({
  'mouseenter': function(){ /* 処理 */ },
  'mouseleave': function(){ /* 処理 */ }
});

selector

こちらは子要素で発生するイベントが親要素に次々に伝搬するイベントバブリングというJavaScriptの特徴を利用した設定方法であるイベントデリゲートを利用してイベントを付与する方法。動的に用意された要素に対しても付与できること、親要素に設定するためメモリを節約できることのメリットがある。

$("ul").on("click","li",function(){ /* 処理 */ });

data

event.dataに引数のdataを格納することができる。

for (let i = 0; i < 3; i++) {
  $('button').eq(i).on('click', { value: i }, function(e){
    alert(e.data.value + '番目のボタンをクリックしました');
  });
}

参考URL

jQuery APIドキュメント(公式)
【jQuery】onメソッドの使用方法

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

PHP で Google reCAPTCHA v3 付きのお問い合わせフォームを作る (1)

はじめに

Google reCAPTCHA v3 を付けたお問い合わせフォームを PHP で作っていきます。
メール送信ライブラリは Swift Mailer を使用します。
reCAPTCHA v3 については こちら

Google reCAPTCHA v3 とは

巷でよく見かける 私はロボットではありません と書かれたチェックボックスの新しいバージョンです。
ですが、 チェックを付けたり、写真を選んだりする必要がなくなりました。
その代わりに、ユーザの行動から 0.0~1.0 のスコアが算出され、 0 に近いほどロボットによる操作の疑いが強く、 1 に近いほど人間による自然な操作の可能性が高いということみたいです。

前提条件

  • PHP 7.3 (これより低くても可)
  • Composer をインストールしておく

事前準備

reCAPTCHA v3 のキーを取得する

まず、 こちら から reCAPTCHA v3 の Admin console にアクセスします。
はじめて Admin console を開いた場合、「新しいサイトを登録する」という画面がされるので、ラベル・reCAPTCHA タイプ(reCAPTCHA v3)・ドメインを入力します。
ラベルは任意の名前で構いません。ドメインは使用するサイトのドメイン、またはローカル開発環境であれば localhost 等を入力してください。
最後に「reCAPTCHA 利用条件に同意する」にチェックを入れて「送信」を押します。
サイトキー と シークレット キー が表示されたらコピーしておきます。

Composer で使用するパッケージをインストールする

使用するパッケージは以下の2つです。

  1. reCAPTCHA PHP client library (Google 公式)
  2. Swift Mailer

composer require でパッケージをダウンロードします。

composer require google/recaptcha && composer require swiftmailer/swiftmailer

パッケージのダウンロードが終わったらひとまず準備は完了です。
ここから実際にコードを書いていきます。

導入手順

1. HTML でフォームを作る

まずは HTML でシンプルなお問い合わせフォームを作っていきます。

index.php
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>お問い合わせフォーム</title>
</head>
<body>
  <form action="send.php" method="POST" name="inquiry">
    <p>
      <label for="name">お名前</label>
      <input type="text" name="name" id="name">
    </p>
    <p>
      <label for="email">メールアドレス</label>
      <input type="email" name="email">
    </p>
    <p>
      <label for="contents">お問い合わせ内容</label>
      <textarea name="contents" id="contents" cols="30" rows="10"></textarea>
    </p>
    <button type="submit">送信する</button>
  </form>
</body>
</html>

2. reCAPTCHA トークンの生成

ここで事前準備の際に取得した サイトキー が必要になります。
JavaScript でトークンの生成とトークンをフォームに hidden で配置するコードを </body> の直前に書いていきます。
<サイトキー> のところは取得したサイトキーを入れてください。
{action: 'inquiry'}inquiry は任意のもので構いません。

index.php
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>お問い合わせフォーム</title>
</head>
<body>
  <form action="send.php" method="POST" name="inquiry">
    <p>
      <label for="name">お名前</label>
      <input type="text" name="name" id="name">
    </p>
    <p>
      <label for="email">メールアドレス</label>
      <input type="email" name="email">
    </p>
    <p>
      <label for="contents">お問い合わせ内容</label>
      <textarea name="contents" id="contents" cols="30" rows="10"></textarea>
    </p>
    <button type="submit">送信する</button>
  </form>

  <script src="https://www.google.com/recaptcha/api.js?render=<サイトキー>"></script>
  <script>
    grecaptcha.ready(function() {
      grecaptcha.execute('<サイトキー>', {action: 'inquiry'}).then(function(token) {
        var rcpt = document.createElement('input');
        rcpt.type = 'hidden';
        rcpt.name = 'token';
        rcpt.value = token;
        document.inquiry.appendChild(rcpt);
      });
    });
  </script>
</body>
</html>

3. reCAPTCHA v3 のスコアを算出して処理を分岐する

フォームからの POST を受け取って、PHPで処理していきます。
ここで事前準備で取得した シークレット キー<シークレット キー> に入れます。
setExpectedAction('inquiry')inquiry は、先ほどトークン犯行の際に指定した {action: 'inquiry'} と同じ名前にします。

ここからが一番重要です。 setScoreThreshold(0.5)0.5 は、人間とロボットを分ける閾値です。
ここの数値が 1 に近いほど厳しい判定になり、 0 に近いほど緩い判定になります。

send.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use ReCaptcha\ReCaptcha as ReCaptcha;

if ($_POST['token']) {
  $response = (new ReCaptcha('<シークレット キー>'))
                  ->setExpectedHostname($_SERVER['SERVER_NAME'])
                  ->setExpectedAction('inquiry')
                  ->setScoreThreshold(0.5)
                  ->verify($_POST['token'], $_SERVER['REMOTE_ADDR']);
  $result = $response->toArray();

  if ($result['success']) {
    echo 'あなたはロボットではありません。';
  } else {
    echo 'あなたはロボットです。';
  }
}

ここまでが reCAPTCHA v3 の基本的な使い方です。

続き → PHP で Google reCAPTCHA v3 付きのお問い合わせフォームを作る (2)

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