Memo

メモ > 技術 > プログラミング言語: PHP

■コーディング規約
■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 = '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;
■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
■文字コードの変換
以下のようにすると、データを「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

Advertisement