■目次
コーディング規約エスケープ正規表現リダイレクトURLエンコード出力のバッファリングソートZIPファイルコマンド実行コンソールで入力を受け付けfile_get_contentsでリクエストするfile_get_contentsでBasic認証を扱うfile_get_contentsでレスポンスヘッダを取得するfile_get_contentsでCookieを扱うスクレイピングenum文字コードの変換外字や機種依存文字の判定PHP+PDOでMySQLのデータ比較を行う際の注意点その他
■コーディング規約
■PHP Standards Recommendations PHP PSR一覧 2017年版 - Qiita https://qiita.com/rana_kualu/items/f41d8f657df7709bda0f
■エスケープ
以下でHTMLをエスケープできる
htmlspecialchars($data)
ENT_QUOTES を指定すると、さらに「'」(シングルクォート)も「'」に変換されて返される 原則この書き方にしておくといい
htmlspecialchars($data, ENT_QUOTES)
■正規表現
■正規表現
if (preg_match('/^[\w\-\/]+$/', $line)) { 〜処理〜 }
■後方参照
if (preg_match('/^\[(.+)\]$/', $line, $matches)) { $data = $matches[1]; 〜処理〜 }
■エスケープしてマッチ
if (preg_match('/^' . preg_quote($url, '/') . '/', $_SERVER['HTTP_REFERER'])) { 〜処理〜 }
■リダイレクト
■内部ページへリダイレクト
header('Location: ./'); exit;
■外部ページへリダイレクト
header('Location: http://www.google.co.jp/'); exit;
■301でのリダイレクト ※未検証 デフォルトではステータスコードが302になるみたい。これは「一時的な移転」を意味する 「永続的な移転」は301となる
header('Location: http://www.google.co.jp/', true, 301); exit;
PHPのheader関数で301などHTTPステータスコードを指定してリダイレクトする https://memorva.jp/memo/dev/php_header_redirect.php phpでリダイレクト(301) - Qiita https://qiita.com/sin_per/items/c55cd10ea611d56605e0 PHPの「正しい」リダイレクト方法と、HTTPステータスコード | WWWクリエイターズ https://www-creators.com/archives/1012
■URLエンコード
標準関数で対応できるが urlencode と rawurlencode が存在し、 urlencode … 半角スペースを「+」に変換し、半角チルダを「%7E」に変換する rawurlencode … 半角スペースを「%20」に変換し、半角チルダは「~」のまま変換しない という違いがある rawurlencode 関数の方がRFC3986に沿った変換を行うため、 URLエンコードは urlencode 関数ではなく rawurlencode 関数を使う方が無難 PHP: urlencode - Manual https://www.php.net/manual/ja/function.urlencode.php PHP: rawurlencode - Manual https://www.php.net/manual/ja/function.rawurlencode.php 【PHP入門】URLエンコードする方法(urlencode) | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト https://www.sejuku.net/blog/25909
■出力のバッファリング
PHPでob_startの使い方。出力タイミングを制御しよう | キノコログ https://kinocolog.com/php_ob_start/
<?php ob_start(); echo '<p>これはテスト1です。</p>'; echo '<p>これはテスト2です。</p>'; echo '<p>これはテスト3です。</p>'; $buffer = ob_get_clean(); $buffer = str_replace('テスト1', '[TEST1]', $buffer); $buffer = str_replace('テスト2', '[TEST2]', $buffer); echo $buffer;
■ソート
2次元配列をソート http://refirio.org/memos/php/multisort/
<?php $array = array( array( 'id' => 10, 'name' => 'hoge', ), array( 'id' => 3, 'name' => 'fuga', ), array( 'id' => 20, 'name' => 'foo', ), array( 'id' => 1, 'name' => 'bar', ), ); //array_multisort(array_column($array, 'id'), $array); //array_multisort(array_map(function ($i) { return $i['id']; }, $array), $array); usort($array, function ($a, $b) { return $a['id'] - $b['id']; }); print('<pre>'); print_r($array); print('</pre>');
2次元配列の2次元目の配列の値でソートをする - Qiita https://qiita.com/tadasuke/items/e7be0d214e02105ab6d8 PHP で二次元配列を特定の値でソートする - Qiita https://qiita.com/shimon_haga/items/c7fcfe58521e79dfc361
■ZIPファイル
■圧縮
$zip = new ZipArchive; if ($zip->open('data.zip', ZipArchive::CREATE)) { $zip->addFile('filename1.png', 'localname1.png'); $zip->addFile('filename2.png', 'localname2.png'); $zip->addFile('filename3.png', 'localname3.png'); $zip->close(); } else { exit('Compress error.'); }
■展開
<?php $zip = new ZipArchive; if ($zip->open('data.zip')) { if ($zip->extractTo('./output/')) { $zip->close(); exit('Complete.'); } else { exit('Extract error.'); } } else { exit('Open error.'); }
■コマンド実行
PHPはコマンド実行関数多すぎだろ - ぱせらんメモ http://d.hatena.ne.jp/pasela/20081217/exec 基本的には shell_exec() でいいと思われる
<?php echo shell_exec('hostname;') ?>
■コンソールで入力を受け付け
以下のようにすると、コンソールから入力を受け付けることができる prompt.php
<?php echo "コードを入力してください: "; $code = trim(fgets(STDIN)); echo "入力されたコードは「" . $code . "」です。";
>php prompt.php コードを入力してください: abcd 入力されたコードは「abcd」です。
■file_get_contentsでリクエストする
HTTPメソッド(CRUD)についてまとめた - Qiita https://qiita.com/Ryutaro/items/a9e8d18467fe3e04068e PHP(GET、POST、PUT、またはDELETE)で要求タイプを検出する [request] | CODE Q&A 問題解決 [日本語] https://code.i-harness.com/ja/q/57a87 PHP の file_get_contents は get どころか post も put も delete も upload もできる - tototoshi の日記 http://tototoshi.hatenablog.com/entry/2014/06/10/011223 LaravelにフォームからPUT/DELETEリクエストを送る - Qiita https://qiita.com/ozhaan/items/c1e394226c1d5acb7f0e ■リクエストする側
<?php // 単純なGET echo file_get_contents('http://localhost/~test/request/target.php'); echo '<hr>'; // ユーザーエージェントを指定してGET echo file_get_contents( 'http://localhost/~test/request/target.php?test=ABC', false, stream_context_create( array( 'http' => array( 'method' => 'GET', 'header' => 'Content-Type: text/html' . "\r\n" . 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)', ) ) ) ); // GET echo file_get_contents( 'http://localhost/~test/request/target.php?test=GETのテスト', false, stream_context_create( array( 'http' => array( 'method' => 'GET', 'header' => 'Content-Type: text/html', ) ) ) ); echo '<hr>'; // POST echo file_get_contents( 'http://localhost/~test/request/target.php', false, stream_context_create( array( 'http' => array( 'method' => 'POST', 'header' => 'Content-Type: application/x-www-form-urlencoded', 'content' => http_build_query( array( 'test' => 'POSTのテスト', ) ) ) ) ) ); echo '<hr>'; // PUT echo file_get_contents( 'http://localhost/~test/request/target.php', false, stream_context_create( array( 'http' => array( 'method' => 'PUT', 'header' => 'Content-Type: text/html', 'content' => http_build_query( array( 'test' => 'PUTのテスト', ) ) ) ) ) ); echo '<hr>'; // DELETE echo file_get_contents( 'http://localhost/~test/request/target.php', false, stream_context_create( array( 'http' => array( 'method' => 'DELETE', 'header' => 'Content-Type: text/html', ) ) ) ); echo '<hr>';
■リクエストされる側
<?php switch ($_SERVER['REQUEST_METHOD']) { case 'GET': echo 'GET:'; break; case 'POST': echo 'POST:'; break; case 'PUT': echo 'PUT:'; break; case 'DELETE': echo 'DELETE:'; break; case 'HEAD': echo 'HEAD:'; break; case 'OPTIONS': echo 'OPTIONS:'; break; case 'TRACE': echo 'TRACE:'; break; case 'CONNECT': echo 'CONNECT:'; break; default: echo 'NG'; break; } print_r($_SERVER); print_r($_REQUEST); parse_str(file_get_contents('php://input'), $parameter); print_r($parameter);
■結果の確認 失敗すると false が返ってくる 空文字が返ってきた場合と区別するために、「===」で比較する必要があるので注意
<?php $result = file_get_contents('http://localhost/~test/request/target.php'); if ($result === false) { echo 'NG'; } else { echo 'OK'; }
参考までに、file_put_contents で保存する場合も失敗すると false が返ってくる 0byteの文字を書き込んだ場合と区別するために、「===」で比較する必要があるので注意
<?php if (file_put_contents('/path/to/file.txt', $message) === false) { echo 'NG'; } else { echo 'OK'; }
■SSLにリクエスト(認証エラーになる場合) 無効な証明書なサイトに、file_get_contents する方法 - Qiita https://qiita.com/izanari/items/f4f96e11a2b01af72846
$context = [ 'ssl' => [ 'verify_peer' => false, 'verify_peer_name' => false, ], ]; $result = file_get_contents('https://localhost/~test/request/target.php', false, stream_context_create($context));
■JSONでリクエスト
$context = [ 'http' => [ 'method' => 'POST', 'header' => 'Content-type: application/json; charset=UTF-8', 'content' => json_encode([ 'param1' => 'aaa', 'param2' => 'bbb', 'param3' => 'ccc', ]), ], 'ssl' => [ 'verify_peer' => false, 'verify_peer_name' => false, ], ]; $result = file_get_contents('https://localhost/~test/request/target.php', false, stream_context_create($context));
■file_get_contentsでBasic認証を扱う
PHP:file_get_contentsでBasic認証する https://salumarine.com/basic-authentication-with-file_get_contents-in-php/
<?php $opts = [ 'http' => [ 'method' => 'GET', 'header' => 'Authorization: Basic ' . base64_encode('username:password') ] ]; echo file_get_contents('http://localhost/~test/request/target.php', false, stream_context_create($opts));
■file_get_contentsでレスポンスヘッダを取得する
file_get_contentsで$http_response_headerを使用するときの注意点 - [PHP + PHP] ぺんたん info http://pentan.info/php/file_get_contents_http_response_header.html $http_response_header で取得できる ただしリクエストに失敗した場合はこの値が上書きされないので、直前に成功したリクエストのレスポンスを取得する可能性がある 混乱を避けるため、リクエストの直前に $http_response_header に null を代入しておくといい
<?php $http_response_header = null; $result = file_get_contents('http://localhost/test/'); print('<pre>'); print_r($http_response_header); exit;
■file_get_contentsでCookieを扱う
【PHP】コマンドラインでサイトへのログイン処理を実装する方法 - とりあえずphpとか http://kimagureneet.hatenablog.com/entry/2015/09/17/014853 CookieのIDを取得
<?php echo file_get_contents('http://localhost/test/session.php'); print('<pre>'); print_r($http_response_header); print('</pre>'); $cookies = array(); foreach ($http_response_header as $header) { $data = explode(':', $header); if ($data[0] == 'Set-Cookie') { $cookies[] = $data[1]; } } print('<pre>'); print_r($cookies); print('</pre>'); $session_id = ''; foreach ($cookies as $cookie) { if (preg_match('/laravel_session=(.+); /', $cookie, $matches)) { $session_id = $matches[1]; } } echo 'session_id=' . $session_id;
取得したCookieのIDとともにリクエスト
<?php $session_id = 'eyJpdiI6ImNIV05pQjZNdVpYODc4MUwra05kMWc9PSIsInZhbHVlIjoia0ttVXF6VjRDK2FrbEJzUGhSXC9ac2FIZGFmS2N6YUlKaG45TndCKzFwNFVOODBVWjlwTTI3TVhHeDFreDRucTYiLCJtYWMiOiI4OTk4ZTQ1NzI0YWM3NjY2MTNjZDViZjFhMTBmMWQwZTViZGQzOTFjN2M3MGRmZDg5Nzg4ZTdlZGVmNDAyZDg4In0%3D'; //$session_id = 'eyJpdiI6Im1iT3ZSV1l5c2NPMExteUhDQmhhS2c9PSIsInZhbHVlIjoiZHE2QTluMWFBQlZWNlh2ZGtLM1Vmb0ZLaU9yN0Y4aXF2NzVROXYwNlwvR2FQMWVRUHpvb2M0d0ZyQVZRNkxMYWMiLCJtYWMiOiI5OTk2MDY0MTUwMjNiMDU3YTczNWIyMzJhNzRmMDRmN2EyOGQyMmQzMmMwOTNmYTdmNzdjYzIzNjJjNWFkMDNhIn0%3D'; //$session_id = 'eyJpdiI6Ikl2aCtidEdSYXZYYmJRbmlXUnFkZEE9PSIsInZhbHVlIjoic0VUNjJOM2tTVStVejlkb2l1VHJGb3J0aVg3WEpaZU9YeVRUcjVUUEpXSVJcL3lzOHZcL21ObWtrQkZucks3dXJEIiwibWFjIjoiODNjNWRhOTlkNzVmOWEyNzNiN2NkMWUyYjVlM2Q0NDdhNTAyNDQ4ODA5NjZjMjQyNjNkN2E2Njg3Mjc0NWYyMCJ9'; $result = file_get_contents( 'http://localhost/test/session.php', false, stream_context_create( array( 'http' => array( 'method' => 'GET', 'header' => "Cookie: laravel_session=" . $session_id . ";\r\n" ) ) ) ); echo $result;
■強制ログインの例
<?php /* * 認証情報をリクエスト */ file_get_contents( 'http://localhost/~test/auth/enter.php', false, stream_context_create( array( 'http' => array( 'method' => 'POST', 'header' => "Content-Type: application/x-www-form-urlencoded;\r\n", 'content' => http_build_query( array( 'username' => 'developer', 'password' => 'abcd1234', ) ) ), ) ) ); /* * 認証後、CookieからセッションIDを取得 */ $cookies = array(); foreach ($http_response_header as $header) { $data = explode(':', $header); if ($data[0] == 'Set-Cookie') { $cookies[] = $data[1]; } } $session_id = ''; foreach ($cookies as $cookie) { if (preg_match('/laravel_session=(.+); /', $cookie, $matches)) { $session_id = $matches[1]; } } /* * ログイン後ページを表示 */ echo file_get_contents( 'http://localhost/~test/auth/home.php', false, stream_context_create( array( 'http' => array( 'method' => 'GET', 'header' => "Cookie: laravel_session=" . $session_id . ";\r\n", ) ) ) ); exit('Complete');
■その他メモ 【Swift】ユーザー認証APIを通した後、同一セッションとしてUIWebViewを表示する - Qiita https://qiita.com/ktanaka117/items/e4921f061f6522ed5a63
■スクレイピング
phpQueryでWEBスクレイピングしてみた | Tips Note by TAM https://www.tam-tam.co.jp/tipsnote/program/post9744.html 今更ながらPHPでスクレイピングをしてみる - Qiita https://qiita.com/zaburo/items/465ca691aebad2b5691e 【php】webサイトから、欲しい情報を3行で取得する方法 - Qiita https://qiita.com/dia/items/3cf963fa89b08b87e8ef 一例だが、以下のように利用できる
<?php require_once('./phpQuery-onefile.php'); $html = file_get_contents('http://localhost/~test/request/target.php'); $doc = phpQuery::newDocument($html); echo '<h1>カテゴリ</h1>'; foreach ($doc['div.category']->find('nav') as $dom_nav){ echo '<h2>' . pq($dom_nav)->find('ul.main li a')->text() . '</h2>'; echo '<ul>'; foreach (pq($dom_nav)->find('ul.sub li a') as $dom_a) { echo '<li>' . pq($dom_a)->text() . ' ... ' . pq($dom_a)->attr('href') . '</li>'; } echo '</ul>'; } exit;
■enum
【Swift入門】enumの使い方をわかりやすくまとめてみた | 侍エンジニア塾ブログ | プログラミング入門者向け学習情報サイト https://www.sejuku.net/blog/35711 Swiftなどではenum(列挙型)を使える これをPHPでも使う方法 Enumを使ってフラグ値を良い感じに扱う - Qiita https://qiita.com/akihiro-iwata/items/b580b225eba48d780e68 PHPで列挙型(enum)を作る - Qiita https://qiita.com/Hiraku/items/71e385b56dcaa37629fe 以下、enumを使った具体的なコード
<?php /* * 列挙型(enum)を作る */ abstract class Enum { private $scalar; public function __construct($value) { $ref = new ReflectionObject($this); $consts = $ref->getConstants(); if (!in_array($value, $consts, true)) { throw new InvalidArgumentException; } $this->scalar = $value; } final public static function __callStatic($label, $args) { $class = get_called_class(); $const = constant($class . '::' . $label); return new $class($const); } final public function valueOf() { return $this->scalar; } final public function __toString() { return (string)$this->scalar; } } // トランプのスート型を定義する。4種類しか値を取らない final class Suit extends Enum { const Spade = 1; const Heart = 2; const Club = 3; const Diamond = 4; } // インスタンス化 $suit = new Suit(Suit::Spade); echo $suit; // spade echo '<hr>'; echo $suit->valueOf(); // spade echo '<hr>'; $suit = new Suit(Suit::Heart); echo $suit; echo '<hr>'; // __callStaticを定義してあるのでnewを使わずこのように書くことができる(PHP5.3以降) $suit = Suit::Spade; if ($suit == Suit::Spade) { echo 'スペードです。'; } else { echo 'スペードではありません。'; } // 存在しない値を指定するとエラー //new Suit('uso800'); // InvalidArgumentException //new Suit(Suit::TEST); // InvalidArgumentException //Suit::TEST; // InvalidArgumentException
■文字コードの変換
以下のようにすると、データを「UTF-8」とみなして「Shitf-JIS」に変換できる
$data = 'テストメッセージ'; $data = mb_convert_encoding($data, 'SJIS', 'UTF-8'); echo $data;
以下のようにすると、データを「UTF-8 → EUC-JP → Shitf-JIS」の順に判定して「Shitf-JIS」に変換できる
$data = array( 'テストメッセージ1', 'テストメッセージ2', 'テストメッセージ3', ); mb_convert_variables('SJIS', 'UTF-8,EUCJP-WIN,SJIS-WIN', $data); print_r($data);
■外字や機種依存文字の判定
/** * JISの半角および、第1、2水準文字であることのチェック。 * @param $data 検査する文字列 * @return true:OK、false:NG * @see 外字や機種依存文字を弾く。第4水準文字は通るが、UTF-8で扱うと問題なくDBでも格納できるのでスルーとしている */ function validator_jis_1or2($data) { $rtn = ''; for ($idx = 0; $idx < mb_strlen($data, 'utf-8'); $idx++) { $str0 = mb_substr($data, $idx, 1, 'utf-8'); // 1文字をSJISにする。 $str = mb_convert_encoding($str0, 'sjis-win', 'utf-8'); //if (strlen($str) == 1) { // 1バイト文字 if ((strlen(bin2hex($str)) / 2) == 1) { // 1バイト文字 $c = ord($str{0}); } else { $c = ord($str{0}); // 先頭1バイト $c2 = ord($str{1}); // 2バイト目 $c3 = $c * 0x100 + $c2; // 2バイト分の数値にする。 if ((($c3 >= 0x8140) && ($c3 <= 0x853D)) || // 2バイト文字 (($c3 >= 0x889F) && ($c3 <= 0x988F)) || // 第一水準 (($c3 >= 0x9890) && ($c3 <= 0x9FFF)) || // 第二水準 (($c3 >= 0xE040) && ($c3 <= 0xEAFF))) { // 第二水準 } else { $rtn .= $str0; } } } if ($rtn != '') { return false; } else { return true; } } /** * 機種依存文字であることのチェック。 * @param $data 検査する文字列 * @return true:OK、false:NG * @see 特定機種依存文字を弾く。(?などの)旧漢字は通る。第一第二水準などの範囲で絞らない場合に使用。 */ function validator_machine_department($data) { $pdc = '??????????????????????????????????????????????????????????????????????????????????¬???'; $pdc_array = Array(); $pdc_text = str_replace(array("\r\n","\n","\r"), '', $data); while ($iLen = mb_strlen($pdc, 'UTF-8')) { array_push($pdc_array, mb_substr($pdc, 0, 1, 'UTF-8')); $pdc = mb_substr($pdc, 1, $iLen, 'UTF-8'); } foreach($pdc_array as $value) { if (preg_match('/(' . $value . ')/', $pdc_text)) { return false; break; } } return true; }
■PHP+PDOでMySQLのデータ比較を行う際の注意点
PHP+PDOでMySQLからデータを取得した際、数値型が文字列型として扱われてしまう よって「===」などで比較を行うと意図した結果にならないことがある サーバ側の設定や追加インストールで対応する方法もあるようだが、 かえって環境依存になってややこしいので「==」で緩やかな比較を行うほうが無難かも PDOでフェッチした数値型カラムの値が文字列で取得されるのでなんとかしようと頑張った。 - erio_nk://memo http://d.hatena.ne.jp/erio_nk/20120621/1340267044 なおLaravelの場合、Attribute Casting により型を厳密に扱うことができるらしい [Laravel5][Eloquent] Attribute Castingによりデータ型を厳密に取り扱う|Laravel|PHP|開発ブログ|株式会社Nextat(ネクスタット) https://nextat.co.jp/staff/archives/140 Laravel 5.5 Pivot Casting - Laravel News https://laravel-news.com/laravel-5-5-pivot-casting
■その他
PHPメモ | refirio.org http://refirio.org/page/memo/php PHP「関数っぽいもの」列伝 - Qiita https://qiita.com/tadsan/items/0d1e79b4baff509e7df1 お前は PHP の歴史的な理由の数を覚えているのか https://www.slideshare.net/ebihara/php-32340906 フラットなPHPからフレームワークへ https://www.slideshare.net/brtriver/php-14295877