- コーディング規約
- エスケープ
- 正規表現
- リダイレクト
- URLエンコード
- 変数
- 配列
- 出力のバッファリング
- 短いハッシュを作成
- 特定範囲の年月日を一覧
- 誕生日から年齢を計算
- ZIPファイル
- コマンド実行
- コンソールで入力を受け付け
- ファイル入出力
- リクエストヘッダを取得する
- file_get_contentsでリクエストする
- file_get_contentsでBasic認証を扱う
- file_get_contentsでレスポンスヘッダを取得する
- file_get_contentsでCookieを扱う
- curlでリクエストする
- FTPでアップロードする
- SSHで接続する
- 特定ディレクトリ配下のファイルを一括処理
- スクレイピング
- Excelの読み書き
- 型宣言
- enum
- 巨大データの扱い
- アノテーション
- アトリビュート
- 文字コードの変換
- 外字や機種依存文字の判定
- メール
- 画像
- QRコードの作成
- QRコードの読み取り
- PHP+PDOでMySQLのデータ比較を行う際の注意点
- コマンドライン引数
- 例外
- PHP8
- その他
コーディング規約
■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でのリダイレクト
以下のようにしてリダイレクトさせることができる。
header('Location: http://localhost/dashboard/');
exit;
ただしこの方法では、リダイレクトのステータスコードが302になり、これは「一時的な移転」を意味する。
URL変更などの「永続的な移転」は301だが、これを指定したければ以下のように明示する。
header('Location: http://localhost/dashboard/', true, 301);
exit;
なお、第2引数は「前に送信された類似のヘッダを置換する」という設定で、引数を省略した場合は true となる。
原則常に true を指定しておいて問題ない。
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
declare(strict_types=1); // 型チェックを厳密にする
function add(int $a, int $b) : int {
return $a + $b;
}
echo add(1, 2); // 計算できる
echo '<hr>';
echo add(2, '3'); // Fatal error「Uncaught TypeError」が発生する
echo '<hr>';
function add5(?int $a) : ?int {
if (is_null($a)) {
return null;
}
return $a + 5;
}
echo add5(2);
echo '<hr>';
echo add5(null); // nullが返される
echo '<hr>';
function add10(int $a = null) : ?int {
if (is_null($a)) {
return null;
}
return $a + 10;
}
echo add10(2);
echo '<hr>';
echo add10(); // nullが返される
echo '<hr>';
echo 'TEST';
PHP 型チェックを厳密にしたい - かもメモ
https://chaika.hatenablog.com/entry/2022/11/21/123048
PHPで型宣言してますか? - RAKUS Developers Blog | ラクス エンジニアブログ
https://tech-blog.rakus.co.jp/entry/20220323/php
「strict_type=1」とあるが「strict_types=1」の間違いだと思われる。
配列
■取得
2次元配列から、特定のカラムのみ取得。
<?php
$array = array(
array(
'id' => 10,
'name' => 'hoge',
),
array(
'id' => 3,
'name' => 'fuga',
),
array(
'id' => 20,
'name' => 'foo',
),
array(
'id' => 1,
'name' => 'bar',
),
);
print('<pre>');
print_r(array_column($array, 'id'));
print('</pre>');
PHPでarray_columnを使う方法を現役エンジニアが解説【初心者向け】 | TechAcademyマガジン
https://techacademy.jp/magazine/29662
■ソート
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
出力のバッファリング
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;
短いハッシュを作成
以下のように作成するのがいいか。
衝突のリスクについては改めて確認しておきたい。必要に応じて衝突チェックを設けるなどが必要になりそう。
<?php
function shortHash($data, $algo = 'CRC32') {
return strtr(rtrim(base64_encode(pack('H*', hash($algo, $data))), '='), '+/', '-_');
}
echo shortHash('hello'); // PWUxGQ
PHPで短いハッシュ - Qiita
https://qiita.com/koriym/items/efc1c419e4b7772b65c0
怒涛のめもめもリンク集 | 短縮ハッシュ値の生成
http://mechsys.tec.u-ryukyu.ac.jp/~oshiro/SiteList/2013/12/17/2537/
特定範囲の年月日を一覧
<?php
// 期間の開始日
$date_begin = new DateTime('2022-12-28');
// 期間の終了日
$date_end = new DateTime('2023-01-04');
$date_end->modify('+1 DAY');
// 日付を取得する間隔
$date_interval = new DateInterval('P1D');
$date_range = new DatePeriod($date_begin, $date_interval, $date_end);
foreach ($date_range as $date){
echo $date->format('Y年m月d日') . '<br>';
}
【PHP】期間内から任意の間隔で日付・時刻を取得する例|DatePeriodクラス
https://blog-and-destroy.com/17471
DateTimeクラスとDateTimeImmutableクラスの違いを理解する - Qiita
https://qiita.com/juve_534/items/b450dc4072bb5539582a
誕生日から年齢を計算
<?php
// 誕生日
$birthday = array(
'year' => 2000,
'month' => 4,
'day' => 10,
);
// 西暦から和暦を取得
list($wareki, $year) = get_wareki($birthday['year'], $birthday['month'], $birthday['day']);
// 誕生日から満年齢を取得
$age = get_age($birthday['year'], $birthday['month'], $birthday['day']);
// 誕生日から数え年を取得
$kazoedoshi = get_kazoedoshi($birthday['year']);
// 結果を表示
echo '<p>誕生日は' , $wareki . $year . '年' . $birthday['month'] . '月' . $birthday['day'] . '日です。</p>';
echo '<p>満年齢は' , $age . '歳です。満年齢は履歴書、パスポート、行政に提出する書類などに使用されます。</p>';
echo '<p>数え年は' , $kazoedoshi . '歳です。数え年は七五三、長寿祝い、厄年などに使用されます。</p>';
exit;
/*
* 西暦から和暦を取得
*/
function get_wareki($year, $month, $day)
{
$date = sprintf('%04d%02d%02d', $year, $month, $day);
if ($date >= 20190501) {
$wareki = '令和';
$year -= 2018;
} elseif ($date >= 19890108) {
$wareki = '平成';
$year -= 1988;
} elseif ($date >= 19261225) {
$wareki = '昭和';
$year -= 1925;
} elseif ($date >= 19120730) {
$wareki = '大正';
$year -= 1911;
} elseif ($date >= 18680125) {
$wareki = '明治';
$year -= 1867;
} else {
$wareki = '';
}
return array($wareki, $year);
}
/*
* 誕生日から満年齢を取得
*/
function get_age($birth_year, $birth_month, $birth_day, $today = null)
{
$birthday = sprintf('%04d%02d%02d', $birth_year, $birth_month, $birth_day);
if (!preg_match('/^\d{8}$/', $today)) {
$today = date('Ymd');
}
return intval(($today - $birthday) / 10000);
}
/*
* 誕生年から数え年を取得
*/
function get_kazoedoshi($birth_year, $this_year = null)
{
if (!preg_match('/^\d{4}$/', $this_year)) {
$this_year = date('Y');
}
return $this_year - $birth_year + 1;
}
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.');
}
コマンド実行
基本的には shell_exec() でいいと思われる。
実行する内容の最後に「 2>&1」を付けておくと、エラーがあったときにその内容を取得できる。
<?php echo shell_exec('hostname;') ?>
PHPはコマンド実行関数多すぎだろ - ぱせらんメモ
http://d.hatena.ne.jp/pasela/20081217/exec
コンソールで入力を受け付け
以下のようにすると、コンソールから入力を受け付けることができる。
prompt.php
<?php
echo "コードを入力してください: ";
$code = trim(fgets(STDIN));
echo "入力されたコードは「" . $code . "」です。";
以下のとおり実行できる。
>php prompt.php
コードを入力してください: abcd
入力されたコードは「abcd」です。
ファイル入出力
ファイル入出力 | PHP Labo
https://www.php-labo.net/tutorial/php/file.html
Webアプリケーションへの同時アクセス対策メモ | refirio.org
https://refirio.org/archives_2024/view/367
リクエストヘッダを取得する
以下のようなリクエストを行ったとき、
$ curl http://obutsudan.local/test.php --header 'x-api-key:ABCDEFG'
test.php で以下のようにすると「ABCDEFG」の文字を取得できる。
この値をもとに、APIへのアクセス制限を設けることができる。
<?php
echo "x-api-key=[" . $_SERVER['HTTP_X_API_KEY'] . "]\n";
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
■基本的なリクエスト
file_get_contents と file_put_contents を使うと、
ローカルファイルの読み書きだけでなくHTTPリクエストできる。
失敗すると 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('http://localhost/~test/request/target.php', $message) === false) {
echo 'NG';
} else {
echo 'OK';
}
■色々なリクエスト(リクエストする側)
<?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);
■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
curlでリクエストする
PHP cURLの色々な使い方 - Qiita
https://qiita.com/wanwanland/items/a5f9574fadd214d7b5c8
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://www.yahoo.co.jp/');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$html = curl_exec($ch);
echo $html;
curl_close($ch);
FTPでアップロードする
PHPでFTP/FTPS/SFTPを使ってアップロードする例
https://blog.ver001.com/php_ftps_sftp/
<?php
$cfg['ftp_host'] = 'example.com'; // 接続先サーバー
$cfg['ftp_user'] = 'refirio'; // ユーザ名
$cfg['ftp_pass'] = 'abcd1234'; // パスワード
function uploadFTP($local_filename, $remote_filename)
{
global $cfg;
// サーバーへ接続
$conn = ftp_connect($cfg['ftp_host']);
// ログイン試行
if (!ftp_login($conn, $cfg['ftp_user'], $cfg['ftp_pass'])) {
echo 'Login Failed';
return;
}
// PASVモードへ変更
ftp_pasv($conn, true);
// ファイルのアップロード
ftp_put($conn, $remote_filename, $local_filename, FTP_BINARY);
// 切断
ftp_close($conn);
}
uploadFTP('file/photo.jpg', '/test/photo.jpg');
echo 'Complete!';
SSHで接続する
PHPには ssh2_connect という命令が用意されているようだが、PHP自体の設定変更が必要なので敷居が高い。
PHPでリモートサーバー上のコマンドをsshで繋いで実行する
https://salumarine.com/running-command-on-remote-server-via-ssh-in-php/
代わりに、Compsoerを使うと比較的容易に接続できる。(内部では fsockopen が呼び出されているみたい。)
PHPでSSHとSFTP phpseclibの使い方
https://oopsoop.com/how-to-use-phpseclib/
以下のようにして、ライブラリを導入する。
$ composer require phpseclib/phpseclib
一例だが以下のようにすると、パスワード認証でSSH接続ができる。
<?php
require_once 'vendor/autoload.php';
use phpseclib3\Net\SSH2;
// 接続先の情報を設定
$host = '203.0.113.1';
$username = 'webmaster';
$password = 'abcd1234';
// SSH2の接続を試行
$ssh = new SSH2($host);
if (!$ssh->login($username, $password)) {
exit('Login Failed.');
}
// コマンドを実行
echo '<pre>';
echo $ssh->exec('ls -la /var/www/html');
echo '</pre>';
一例だが以下のようにすると、鍵認証でSSH接続ができる。
接続はIPアドレスでも可能。あわせてポート番号も変更している。
<?php
require_once 'vendor/autoload.php';
use phpseclib3\Net\SSH2;
use phpseclib3\Crypt\PublicKeyLoader;
// 接続先の情報を設定
$host = '203.0.113.1';
$port = '10022';
$username = 'ec2-user';
$key = PublicKeyLoader::load(file_get_contents('path/to/key.pem'));
// SSH2の接続を試行
$ssh = new SSH2($host, $port);
if (!$ssh->login($username, $key)) {
exit('Login Failed.');
}
// コマンドを実行
echo '<pre>';
echo $ssh->exec('ls -la /var/www/html');
echo '</pre>';
SSH接続は禁止されているが、SFTP接続は許可されている…という場合、以下のように接続する。
<?php
require_once 'vendor/autoload.php';
use phpseclib3\Net\SFTP;
// 接続先の情報を設定
$host = '203.0.113.1';
$port = '10022';
$username = 'webmaster';
$password = 'abcd1234';
// SFTPの接続を試行
$sftp = new SFTP($host, $port);
if (!$sftp->login($username, $password)) {
exit('Login Failed.');
}
// コマンドを実行
echo '<pre>';
echo $sftp->pwd();
echo '</pre>';
echo '<pre>';
print_r($sftp->rawlist('/var/www'));
echo '</pre>';
ディレクトリごとダウンロードはできないようなので、ファイル一覧をもとに一つずつダウンロードすることになりそう。
一例だが、以下のように処理できる。
$remote_dir = '/var/www/html';
$local_dir = './path/to/download';
$dir = $sftp->rawlist($remote_dir);
foreach ($dir as $entry) {
if ($entry['filename'] == '.' || $entry['filename'] == '..') {
continue;
}
if ($sftp->get($remote_dir . '/' . $entry['filename'], $local_dir . '/' . $entry['filename'])) {
echo '<p>' . $entry['filename'] . ' のダウンロードに成功しました。</p>';
} else {
echo '<p>' . $entry['filename'] . ' のダウンロードに失敗しました。</p>';
}
}
一例だが、以下のようにすればアップロードもできる。
if (!$sftp->put('/var/www/vhosts/xxx/test.txt', './test.txt', SFTP::SOURCE_LOCAL_FILE)) {
exit('Upload Failed.');
}
その他SFTPでの操作は、以下などが参考になりそう。
PHPでSSHとSFTP phpseclibの使い方
https://oopsoop.com/how-to-use-phpseclib/
PHPでSFTPに接続する方法
https://sftptogo.com/blog/jp/php-sftp-jp/
特定ディレクトリ配下のファイルを一括処理
一例だが以下のようにすると、特定ディレクトリ配下のファイル内容を表示できる。
show_files('./data');
function show_files($target_dir)
{
$targets = array();
if ($dir = scandir($target_dir)) {
foreach ($dir as $entry) {
if ($entry == '.' or $entry == '..') {
continue;
}
$targets[] = $entry;
}
}
foreach ($targets as $target) {
if (is_dir($target_dir . '/' . $target)) {
show_files($target_dir . '/' . $target);
} elseif (is_file($target_dir . '/' . $target)) {
$result = file_get_contents($target_dir . '/' . $target);
if ($result === false) {
echo 'ERROR';
} else {
echo $target_dir . '/' . $target;
echo '<pre><code>' . $result . '</code></pre>';
}
}
}
return;
}
スクレイピング
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;
Excelの読み書き
現在PHPExcelは非推奨になっている。
後継としてPhpSpreadsheetがある。
PHPExcelとPhpSpreadsheetの比較 #PHP - Qiita
https://qiita.com/C_HERO/items/1b4b5ed467b6bf390fcd
PHPでExcelを読み書きできるPhpSpreadsheetのインストールと簡単な使い方 | 株式会社レクタス
https://www.rectus.co.jp/archives/18375
[PHP]PHPExcelとPhpSpreadsheetをサンプルコードで比較
https://zenn.dev/c_hero/articles/82f32cb01bcb67
PhpSpreadsheetでExcelを読み書きしてExcelとしてダウンロードする #PHP - Qiita
https://qiita.com/haruna-nagayoshi/items/bccc4b844e909608f514
LaravelとPHP SpreadsheetでExcelファイルを簡単に操作する方法 | ユアスク
https://your-school.jp/laravel-phpspreadsheet/248/
phpによるサイズの大きなExcelデータファイルの読み込み - LeafWindow
https://www.leafwindow.com/read-large-excel-file-with-php/
以下でライブラリを導入する。
$ composer require phpoffice/phpspreadsheet
Excelファイルへの書き込みは、以下のようにして行なえる。
<?php
require 'vendor/autoload.php';
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
//use PhpOffice\PhpSpreadsheet\IOFactory;
// インスタンスを作成
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
// データを定義
$data = [
['名前', 'ひらがな', '年齢', '性別'],
['太郎', 'たろう', '12才', '男'],
['花子', 'はなこ', '15才', '女'],
];
$sheet->fromArray($data, null, 'A1');
// Excelファイルを保存
$writer = new Xlsx($spreadsheet);
//$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
$writer->save('test_' . date('Ymd') . '.xlsx');
exit('Complete');
Excelファイルからの読み込みは、以下のようにして行なえる。
<?php
require 'vendor/autoload.php';
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
//use PhpOffice\PhpSpreadsheet\Spreadsheet;
// ファイルを読み込み
$reader = new Xlsx();
//$reader = IOFactory::createReader('Xlsx');
$spreadsheet = $reader->load('sample.xlsx');
// シートを読み込み
$sheet = $spreadsheet->getActiveSheet();
// シートの内容を配列にして返す
$sheetData = $sheet->toArray();
var_dump($sheetData);
exit;
以下のようにすると、シート名とともに複数のシートを読み込める。
<?php
require 'vendor/autoload.php';
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
//use PhpOffice\PhpSpreadsheet\Spreadsheet;
// ファイルを読み込み
$reader = new Xlsx();
//$reader = IOFactory::createReader('Xlsx');
$spreadsheet = $reader->load('sample1.xlsx');
// シート数を取得
$sheetsCount = $spreadsheet->getSheetCount();
for ($i = 0; $i < $sheetsCount; $i++) {
// シートを切り替え
$spreadsheet->setActiveSheetIndex($i);
// シートを読み込み
$sheet = $spreadsheet->getActiveSheet();
// シート名を取得
$sheetName = $sheet->getTitle();
// シートの内容を配列にして返す
$sheetData = $sheet->toArray();
print($sheetName);
print_r($sheetData);
}
exit;
型宣言
※未検証。
PHP: 型宣言 - Manual
https://www.php.net/manual/ja/language.types.declarations.php
PHPで型宣言してますか? - RAKUS Developers Blog | ラクス エンジニアブログ
https://tech-blog.rakus.co.jp/entry/20220323/php
PHPで型を意識し安全なプログラムを書く - Qiita
https://qiita.com/minato-naka/items/cc9da5fc1bc8cc4240a8
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
巨大データの扱い
■yield
yieldって何?PHPのジェネレータについてまとめてみた #generator - Qiita
https://qiita.com/falya128/items/53c4392b3695c12f9014
PHPのyield構文でジェネレータを使おう!サンプル付き解説 | 侍エンジニアブログ
https://www.sejuku.net/blog/74314
今回の検証用データは以下で作成する
個人情報テストデータジェネレーター
https://testdata.userlocal.jp/
以下はyieldを使わずに配列で処理するコード。
何度か実行したところ、メモリの消費量は「13.654 MB」前後となった。
// 配列で処理
function readCsv()
{
$records = [];
$file = fopen("dummy.csv", "r");
// 一行目は無視
$dummy = fgetcsv($file);
// データを取得
while ($record = fgetcsv($file)) {
$records[] = $record;
}
return $records;
}
echo "<p>実行開始:" . round(memory_get_peak_usage() / (1024 * 1024), 3) . " MB</p>\n";
$generator = readCsv();
foreach ($generator as $record) {
echo "<p>" . $record[0] . " / " . $record[2] . " / " . $record[4] . "</p>";
}
echo "<p>実行終了:" . round(memory_get_peak_usage() / (1024 * 1024), 3) . " MB</p>\n";
以下はyieldで処理するコード。
何度か実行したところ、メモリの消費量は「0.417 MB」前後となった。
// ジェネレータで処理
function readCsv()
{
$file = fopen("dummy.csv", "r");
// 一行目は無視
$dummy = fgetcsv($file);
// データを取得
while ($record = fgetcsv($file)) {
yield $record;
}
}
echo "<p>実行開始:" . round(memory_get_peak_usage() / (1024 * 1024), 3) . " MB</p>\n";
$generator = readCsv();
foreach ($generator as $record) {
echo "<p>" . $record[0] . " / " . $record[2] . " / " . $record[4] . "</p>";
}
echo "<p>実行終了:" . round(memory_get_peak_usage() / (1024 * 1024), 3) . " MB</p>\n";
上記のとおり、配列にデータを貯め続けないのでメモリ使用量を抑えて処理できる。
データベース連携も含めた検証コードが以下にある。
Dropbox\技術\PHP\yield
■その他
CSVファイルをダウンロードする機能を試作する。
大量データでメモリエラーなどになることを確認し、
「一定数取得してから結合」
を繰り返せば対応できるかも確認したい。
以下なども参考になるか。
PHPで容量の大きいデータをCSV出力するときに工夫したこと - Qiita
https://qiita.com/shosho/items/1a83ee7df82acca2274d
PHPで大容量CSV書き出しを高速で処理する - Qiita
https://qiita.com/kurosuke1117/items/112d12df6f88aaf85f10
大容量ファイルをphpでダウンロードさせる方法[自分でなんとかするWeb]
https://every-rating.com/php/php.html
【PHP】大量データをfputcsvで瞬時にCSVファイル出力する方法
https://i-407.com/blog/tech/n2/
試作したプログラムが以下にある。
技術\PHP\outputdata
アノテーション
PHPのアノテーションの仕組みとメリット・デメリット / About PHP annotations - Speaker Deck
https://speakerdeck.com/hiro_y/about-php-annotations
PHPアノテーションの秘訣!5つのステップで理解・活用する方法 - Japanシーモア
https://jp-seemore.com/web/5644/
リフレクションを使用すると、アノテーションを取得できる。
フレームワークのSymfonyでは、アノテーションが多用されている。
<?php
$reflection = new ReflectionClass('Sample');
echo 'getName: ' . $reflection->getName() . '<br>';
echo 'getFileName: ' . $reflection->getFileName() . '<br>';
echo '<br>';
echo 'class: ' . $reflection->getDocComment() . '<br>';
echo '<br>';
echo 'property name: ' . $reflection->getProperty('name')->getDocComment() . '<br>';
echo 'property age: ' . $reflection->getProperty('age')->getDocComment() . '<br>';
echo '<br>';
$docComment = $reflection->getMethod('specialMethod')->getDocComment();
echo 'method specialMethod: ' . $docComment . '<br>';
if (preg_match('/@CustomAnnotation\("(.*)"\)/', $docComment, $matches)) {
echo 'method specialMethod CustomAnnotation: ' . $matches[1] . '<br>';
}
echo '<br>';
$docComment = $reflection->getMethod('createItem')->getDocComment();
echo 'method createItem: ' . $docComment . '<br>';
if (preg_match('/@Route\("(.*)", methods={"(.*)"}\)/', $docComment, $matches)) {
echo 'method createItem path: ' . $matches[1] . '<br>';
echo 'method createItem method: ' . $matches[2] . '<br>';
}
exit;
/**
* @SampleClass
*/
class Sample
{
/**
** @var string
*/
public $name;
/**
* @var int
*/
public $age;
/**
* @CustomAnnotation("special value")
*/
public function specialMethod()
{
return;
}
/**
* @Route("/api/items", methods={"POST"})
*/
public function createItem()
{
return;
}
}
アトリビュート
※未検証。
PHP 8.0の新機能「アトリビュート」を初体験
https://zenn.dev/kou_hikaru/articles/3af612bfa28bba
Attributesで実現するPHP8時代のバリデータ #PHP - Qiita
https://qiita.com/tadsan/items/c041716650fbe2427663
PHP 8 で導入される Attributes 事始め #PHP8 - Qiita
https://qiita.com/il-m-yamagishi/items/73fc75fb42879d1353d8
PHP8から、アノテーションに似た仕組みとしてアトリビュートを利用できる。
リフレクションクラスでコメントを参照するのは同じだが、アトリビュートは言語機能として組み込まれているので高速らしい。
またアノテーションは文字列で取得するので解析が必要だが、アトリビュートはリフレクションのメソッドから直接参照できるらしい。
Symfonyではアノテーションが多用されているが、バージョン5.2からアトリビュートに対応している。
文字コードの変換
以下のようにすると、データを「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 = '?????????????????????????????????????????????????????????????????????????@?A?B?C?D?E?F?G?H?I¬?U?V?W';
$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】こんな関数あったんだ!wordwrapでラクして文字列を分割する | 侍エンジニアブログ
https://www.sejuku.net/blog/49999
1行1000バイトを超えると文字化けするメール | Points & Lines
https://pointsandlines.jp/server-side/php/over-1000-bytes-on-a-line
PHP: wordwrap - Manual
https://www.php.net/manual/ja/function.wordwrap.php
以下の方法なら意図したとおりに改行された
メール本文が一行1000バイトを超えると文字化ける問題 - Qiita
https://qiita.com/saekis/items/7ef6b0d6a9a7180e3ebe
画像
スマホから写真を撮影したものを保存すると、画像が回転した状態で表示されることがある。
Exif回転情報を読み取り、それに従って画像を回転させる必要がある。
PHPで写真のExif回転に対応する
https://blog.ver001.com/php_exif_orientation/
ブラウザによる画像向き補正確認用ページ
https://blog.knjcode.com/browser-image-rotation-test/
UIImageOrientation / EXIF orientation sample images - Matt Galloway
https://www.galloway.me.uk/2012/01/uiimageorientation-exif-orientation-sample-images/
GDまたはImagick拡張でexif情報を削除する - Qiita
https://qiita.com/mgng/items/416eaacf01e424cdca29
iPhoneで縦向き撮った画像が横向きに回転しちゃうよーん! - Qiita
https://qiita.com/HorikawaTokiya/items/7d469ec5b6660a7e4c96
iPhoneで撮影したHEIC型式の画像ファイルのExif情報をExifToolで取得する - ふぁメモ
https://fa.hatenadiary.jp/entry/20200502/1588373190
■画像の回転(回転情報を読み取る)
<?php
$file_path = 'path/to/image.jpg';
$exif_data = exif_read_data($file_path);
if (isset($exif_data['Orientation'])) {
$flip = '';
$rotate = '';
switch($exif_data['Orientation']) {
case 1:
$flip = '反転していない';
$rotate = '回転していない';
return;
case 8:
$flip = '反転していない';
$rotate = '右に90度回転している';
break;
case 3:
$flip = '反転していない';
$rotate = '180度回転している';
break;
case 6:
$flip = '反転していない';
$rotate = '右に270度回転している';
break;
case 2:
$flip = '反転している';
$rotate = '回転していない';
break;
case 7:
$flip = '反転している';
$rotate = '右に90度回転している';
break;
case 4:
$flip = '反転している';
$rotate = '180度回転している';
break;
case 5:
$flip = '反転している';
$rotate = '右に270度回転している';
break;
}
echo '画像 ' . $file_path . ' は[' . $flip . '][' . $rotate . ']画像です。';
} else {
echo '画像 ' . $file_path . ' はOrientationの無い画像です。';
}
echo 'Exif情報は以下のとおりです。';
print('<pre>');
print_r($exif_data);
print('</pre>');
exit;
■画像の回転(回転&反転させる)
GDのimageflip関数やimagerotate関数で回転させ、それをimagejpegで保存する…のような方法で対応できる。
ただしHEIC形式には有効な手段では無いと思われる。詳細は後述の「HEICの対応」を参照。
PHPで写真のExif回転に対応する
https://blog.ver001.com/php_exif_orientation/
■画像の回転(HEICの対応)
※若干釈然としないまま。
最近のiOSでは「HEIF」という画像形式が採用されている。(拡張子は「.heic」となっている。)
これは「High Efficiency Image File Format」の略で、高効率のフォーマット画像となっている。
HEIFとJPEGどっちを選ぶ?空き容量対策にもなるiPhoneカメラの保存形式を比較してみた | あいこうらのさくっとふぉとらいふ
https://photolog.aiko15.com/10141/
このファイルの場合、アップロードすると「Exifを削除したうえでJpeg形式に変換する」とされてしまうらしい。
iPhoneの高効率フォーマット(HEIC)だとinput[type=file]でExifが読み取れない - Qiita
https://qiita.com/dameyellow/items/1ed487216f563c871cb5
以下のページによると
・iOSがExifを削除するのは意図された挙動。
・ユーザのプライバシーを保護するため、Exifを削除している。
・この挙動は、現状どうすることもできない。
らしい。
アップロードした後に、ユーザ自身で画像を回転させたりできるUIを用意する…くらいしか無いか。
php - Image upload from iPhone strips exif data - Stack Overflow
https://stackoverflow.com/questions/16297730/image-upload-from-iphone-strips-exif-data
…と思ったが、2023年3月時点でiPhone12mini実機で試すと、普通にExifでOrientationを参照できた。
「保存処理を書かずに $_FILES['upfile']['tmp_name'] の画像を直接参照すると駄目なのでは」という意見もあったが、それでも参照できた。
プライバシーの都合でExifを削除するようにしたが、不満が多かったのでその後のiOSアップデートで削除しないようにした。
…のかもしれないが、詳細は不明。
QRコードの作成
Composerで「endroid/qr-code」を使うのが定番の対応みたい。
endroid/qr-code: QR Code Generator
https://github.com/endroid/qr-code
PHP endroid / qr-codeを用いて、QRコードを表示 - Qiita
https://qiita.com/hirai-11/items/93337bf926437cc5b3b7
5分で出来る!PHPでQRコードを生成する方法 | あぱーブログ
https://blog.apar.jp/program/13204/
PHP QRコード生成ライブラリ「endroid/qr-code」 | 技術情報 | アプリ関連ニュース | ギガスジャパン
https://www.gigas-jp.com/appnews/archives/11128
■実際に導入してみたときのメモ
Composerは「Dropbox\サーバ\XAMPP.txt」の「PHPのComposerを使う」の手順で導入済みとする。
>composer -V
Composer version 2.4.4 2022-10-27 14:39:29
>composer require endroid/qr-code
以下のコードを作成し、ブラウザからアクセスするとQRコードが表示される。
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Endroid\QrCode\Builder\Builder;
use Endroid\QrCode\Encoding\Encoding;
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelHigh;
use Endroid\QrCode\Label\Alignment\LabelAlignmentCenter;
use Endroid\QrCode\Label\Font\NotoSans;
use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeMargin;
use Endroid\QrCode\Writer\PngWriter;
$result = Builder::create()
->writer(new PngWriter())
->writerOptions([])
->data('Test message by QrCode.') // テキストの検索になる
//->data('https://refirio.net/') // ブラウザで遷移する
//->data('tel:09012345678') // 電話をかける
->encoding(new Encoding('UTF-8'))
->errorCorrectionLevel(new ErrorCorrectionLevelHigh())
->size(200)
->margin(10)
->roundBlockSizeMode(new RoundBlockSizeModeMargin())
//->logoPath(__DIR__.'/assets/symfony.png')
->labelText('これはラベルです。')
->labelFont(new NotoSans(16))
->labelAlignment(new LabelAlignmentCenter())
->validateResult(false)
->build();
header('Content-Type: '.$result->getMimeType());
echo $result->getString();
QRコードの読み取り
「khanamiryan/php-qrcode-detector-decoder」を使うことで読み取りができる。
endroid/qr-code も内部ではこれを呼び出しているみたい
khanamiryan/php-qrcode-detector-decoder: This is a PHP library to detect and decode QR-codes. This is first and only QR code reader that works without extensions.
https://github.com/khanamiryan/php-qrcode-detector-decoder
■実際に導入してみたときのメモ
Composerは「Dropbox\サーバ\XAMPP.txt」の「PHPのComposerを使う」の手順で導入済みとする。
>composer -V
Composer version 2.4.4 2022-10-27 14:39:29
>composer require khanamiryan/qrcode-detector-decoder
以下のコードを作成し、ブラウザからアクセスするとQRコードの読み取り結果が表示される。(エラーへの対応は、後述の「トラブル対応」を参照。)
具体的には「Test message by QrCode.」「https://refirio.net/」「tel:09012345678」といったテキストを取得できた。
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Zxing\QrReader;
$qrcode = new QrReader('data/test.png'); // QRコードの画像
echo $qrcode->text();
■トラブル対応
GitHubに「PHP >= 8.1」と書かれているものの、PHP8.1環境で実行すると以下のエラーになった。
Fatal error: Declaration of Zxing\GDLuminanceSource::isCropSupported() must be compatible with Zxing\LuminanceSource::isCropSupported(): bool
in /var/www/html/qrcode-read/vendor/khanamiryan/qrcode-detector-decoder/lib/GDLuminanceSource.php on line 169
PHPUnitのsetUp()を使ったら「must be compatible」とエラーが表示される - Qiita
https://qiita.com/ismt7/items/fcc898f38ee161b38ef4
vendor\khanamiryan\qrcode-detector-decoder\lib\GDLuminanceSource.php
の169行目を以下のように調整してみる。
public function isCropSupported()
↓
public function isCropSupported(): bool
これで読み取り結果を表示できた。
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
コマンドライン引数
コマンドやファイル名も含めて、引数は $_SERVER['argv'] に格納される。
これを解析すれば、コマンドライン引数によって処理の分岐などを設けることができる。
それ以外にも、$argv や getopt を使うことでもコマンドライン引数を扱うことができるみたい。(未検証。)
PHPでコマンドライン引数とかオプションを取得する方法 | PisukeCode - Web開発まとめ
https://pisuke-code.com/php-ways-to-get-cmd-line-args/
例外
PHP7からは、文法エラーも例外としてキャッチできる。
<?php
try {
include 'ng.php';
} catch (ParseError $e) {
echo 'ParseError: ' . $e->getMessage();
}
echo '[Complete]';
例えば ng.php の内容が以下だとする。(行の最後にセミコロンが無いので文法違反。)
<?php
echo 'TEST'
echo 'TEST'
これを実行すると、以下のように完了される。
「ParseError」の部分が重要で、例えばここが「Exception」だと、通常どおり文法エラーで処理を完了できない。
ParseError: syntax error, unexpected 'echo' (T_ECHO), expecting ',' or ';'[Complete]
PHP7調査(23)致命的エラーが例外としてキャッチできるようになった - Qiita
https://qiita.com/hnw/items/4e2d47d269a26025a726
PHP8
PHP: PHP 8.0.0 Release Announcement
https://www.php.net/releases/8.0/ja.php
【PHP8.0】PHP8.0の新機能 - Qiita
https://qiita.com/rana_kualu/items/fe7998fbe773544d5d25
【PHP8.1】PHP8.1の新機能 - Qiita
https://qiita.com/rana_kualu/items/a6601b49e0591eb42200
その他
PHPメモ | refirio.org
http://refirio.org/page/memo/php
サーバメモ | refirio.org
http://refirio.org/memos/server/?file=Programming.txt
PHP「関数っぽいもの」列伝 - Qiita
https://qiita.com/tadsan/items/0d1e79b4baff509e7df1
お前は PHP の歴史的な理由の数を覚えているのか
https://www.slideshare.net/ebihara/php-32340906
フラットなPHPからフレームワークへ
https://www.slideshare.net/brtriver/php-14295877