■コーディング規約
■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;
■外部ページへリダイレクト
■301でのリダイレクト
以下のようにしてリダイレクトさせることができる
ただしこの方法では、リダイレクトのステータスコードが302になり、これは「一時的な移転」を意味する
URL変更などの「永続的な移転」は301だが、これを指定したければ以下のように明示する
なお、第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」です。
■ファイル入出力
Webアプリケーションへの同時アクセス対策メモ | refirio.org
https://refirio.org/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 = '210.173.31.94';
$port = '9022';
$username = 'terraport';
$password = 'y7M30Whz';
// 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;
■型宣言
※未検証
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
■文字コードの変換
以下のようにすると、データを「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】こんな関数あったんだ!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