スキップしてメイン コンテンツに移動

投稿

ラベル(PHP)が付いた投稿を表示しています

PHPのXMLReaderで発生したエラーをExceptionとしてキャッチする方法

PHPのXMLReaderの内部で発生したエラーは、Exceptionではないためtry~catchブロックで捕まえることができません。 Exceptionとして捕まえるためには、set_error_handlerを使って、自前でエラーをExceptionに変換して投げる必要があります。 下記のコードで実現できます。 <?php // operationにエラーを発生させる可能性のある処理ブロックを渡す。 function caputureNativeErrorAndThrowIt(callable $operation) { $errorReportingLevel = error_reporting(E_ALL); set_error_handler(function( int $serverity, string $message, string $file, int $line ): void{ throw new \ErrorException($message, 0, $serverity, $file, $line); }); try { $operation(); } finally { restore_error_handler(); error_reporting($errorReportingLevel); } } // 下記はcaputureNativeErrorAndThrowIt関数をXMLReaderで使用した場合の例です。 $path = 'path_to_invalid_xml_file'; $reader = new \XMLReader(); $reader->open($path); caputureNativeErrorAndThrowIt(function() use($reader){ try { while($reader->read()){ // do something } } finally{ $reader->close()

PHPで特定の時刻の差分を秒数で計算するプログラム

PHPで特定の時刻の差分を秒数で計算する方法は、いろいろ考えられますが、ここではDateTimeクラスを使った方法を紹介します。 <?php // $date1, $date2はDateTimeで受け入れられる時刻の文字列表現であればOKです。 function diffSeconds(string $date1, string $date2) { return (new DateTime($date1))->getTimestamp() - (new DateTime($date2))->getTimestamp(); } // 下記のようにして使います。 diffSeconds('2020-06-05 13:54:32', '2020-04-21 22:12:22');

PHPでメモリにバッファしながらファイルに書き込む方法

最近のディスクは高速なのであまりIO Waitを意識することは少なくなりましたが、書き込み速度の遅いディスク(書き込みのコマンドのコストが高い)に何度も書き込みを実行すると、書き込みのトータルの時間が長くなる場合があります。 この投稿では、PHP版の簡単な「なんちゃって」BufferedWriterクラスを書いてみました。 通常であれば単純にfile_put_contentsメソッドを使って下記のように書けば十分です。 file_put_contents($filename, $chunk, FILE_APPEND|LOCK_EX); しかし、大量のデータを何度も書き込む場合に、特にパフォーマンスの観点から下記の問題が出てきます。 file_put_contentsはfopenによるファイルオープン、書き込み、クローズを実行するので手軽ではありますが、書き込みのオーバーヘッドは高くなります。 file_put_contentsに限ったことではないが、メモリにある程度バッファしてから書き込むことで、書き込みの実行回数を減らすことでパフォーマンスを高めることができる(例: JavaのBufferedWriter)。 というわけで、上記の問題を解決すべく、PHP版「なんちゃって」BufferedWriterクラスを書いてみました。 <?php class BufferedStreamFileWriter { private $path; private $bufferSize = 0; private $bufferLimit; private $buffer = ''; // constructor内でfopen public function __construct($path, $bufferLimit, $mode='a') { $this->path = $path; $this->bufferLimit = $bufferLimit; $this->handle = fopen($path, $mode); } // このメ

PHPのlibxmlでのエラーの扱い方

PHPでXML形式のデータを扱うにはlibxmlライブライを使うことが一般的です。 libxml内でのエラー発生時の処理は、libxml_use_internal_errorsの設定で、下記の2通りの設定が可能です。 libxml_use_internal_errors(false): Exceptionとして投げる。 libxml_use_internal_errors(true): libxml_get_errors()で取得。※libxml_clear_errorsで事前にエラーをクリアしておく方が無難。 libxml_use_internal_errors(true)に設定して、Exceptionを発生させないようにする関数のサンプルを示します。 $excecutionに実際のXML処理を渡します。 function executeInLibXmlUseInternalErrors(callable $execution) { // 現在のlibxml_use_internal_errorsの設定を処理後に戻すために保存 $useErrors = libxml_use_internal_errors(); try {   libxml_clear_errors(); libxml_use_internal_errors(true); return $execution(); } finally{ // 処理が終わったらlibxml_use_internal_errorsの設定をもとに戻す libxml_use_internal_errors($useErrors); } } // 下記はXPathでDOMDodumentからXMLの要素を取得する場合の使用例です。 // XMLの処理中にもExceptionが発生しないので、プログラム内で処理することができます。 $xpath = '/xpath'; $file = '/path_to_xml_file'; $result = executeInLibXmlUseInternalErrors(function

register_shutdown_functionを使ってPHPのFatal Error発生後の処理を制御

PHP 7以降では、PHPのエラーは基本的にErrorクラスとして扱われ、try-catchブロックで捕まえることができるようになりました。(Catchable Fatal Error) ちなみにPHP 7では下記の2つのクラスがThrowableクラスを継承しており、両方捕まえるときは、try-catchブロックでThrowableをcatchするように書けばOKです。 Exception Error ただし、一部のFatal Error(例: Memory Exhausted Error)はtry-catchブロックで捕まえることができません。 Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate 9792 bytes) in .... この場合は、register_shutdown_functionを使って、PHPのプログラムが終了したときにエラー処理をするしかなさそうです。 register_shutdown_functionとerror_get_lastを使ってエラー処理をする関数のコード例を示します。 <?php // PHPのError定数 => 文字列表現の連想配列 const ERROR_CODE_TO_STRING = [ E_ERROR => 'E_ERROR', E_WARNING => 'E_WARNING', E_PARSE => 'E_PARSE', E_NOTICE => 'E_NOTICE', E_CORE_ERROR => 'E_CORE_ERROR', E_CORE_WARNING => 'E_CORE_WARNING', E_COMPILE_WARNING => 'E_COMPILE_WARNING', E_USER_ERROR => 'E_USER_ERROR', E_USER_WARNING => '

PHPでファイルを指定した行数ごとに分割

ファイルを指定した行数ごとに分割するためには、Linuxのsplitコマンドを使えば簡単に実現できます。 PHPではexec関数にsplitコマンドを渡して実行すればよいですが、下記の弱点があります。 Linuxのコマンドに依存 (PHPの場合はほとんどLinux環境で動作させることが普通なのでそこまで問題にならないかも知れません)。 exec関数は慎重に引数を渡さないと、OSコマンドインジェクション脆弱性を引き起こす可能性がある。 そこで、今回はPHPでファイルを指定した行数ごとに分割するプログラムを書いてみました。 <?php class FileSplitter { private $lines; private $fileCount; public function split($filePath, $linesPerFile, $outputDir) { $this->fileCount = 0; $this->lines = null; $file = new \SplFileObject($filePath); $lineCount = 0; try{ while (!$file->eof()) { if($lineCount % $linesPerFile === 0) { $this->writeToFile($this->generateOutputFilePath($outputDir, $file)); } $this->lines[] = $file->fgets(); $lineCount++; } $this->writeToFile($this->generateOutpu

MySQLでGROUP_CONCATしたフィールドに対して疑似的にLIMITを実現する方法

MySQLでGROUP_CONCATしたフィールドに対して疑似的にLIMITを実現するには、GROUP_CONCATで生成された文字列に対して、SUBSTRING_INDEXを使って文字列を切り出す方法が簡単です。 # 下記はid, codeをカラムに持つテーブルで、codeカラムでGROUP BYして、codeごとにidをlimitで取得する例です。 SELECT code ,SUBSTRING_INDEX(GROUP_CONCAT(id ORDER BY id DESC), ',', :limit) # :limitの部分に取得したい件数を指定。 FROM table GROUP BY code 長所は、下記のように条件を指定して、LIMIT句で取得件数を指定したクエリを何度も発行する必要がないところです。 特に、一回あたりクエリの発行コストが高い場合は、GROUP_CONCATを使って一度に取得したほうが最終的な実行時間をかなり節約することができます。 SELECT id FROM table WHERE code = 'A' ORDER BY id DESC LIMIT :limit; ただし、短所も多いので、使用する際は、これらの短所について十分に考慮したうえで使ってください。 GROUP_CONCATで生成された文字列に対して、SUBSTRING_INDEXを使って文字列を切り出すという文字列処理なので、無駄が多い。 特にGROUP_CONCATで生成された元の文字列が長い場合。 GROUP_CONCATの区切り文字が、GROUP_CONCATされる元の文字列に含まれていると正しくLIMITされない。 例:GROUP_CONCATされる元の文字列にカンマが含まれているのに、カンマを区切り文字で指定している場合。 GROUP_CONCATの最大文字数制限を超えた場合は、機能しない。 MySQLのGROUP_CONCATの最大文字数制限は、「SHOW VARIABLES LIKE '%group_concat%';」で調べられます。デフォルト値は1024のようです。

PHPで現在設定されているerror reporting levelを文字列で表示

PHPのerror reporting levelは、bit形式で表現されていて、error_reporting関数で取得しても、何が設定されているのかすぐにはわかりづらいと思います。 ちょっとしたプログラムですが、error_reporting_levelを文字列形式で出力するプログラムを書いてみました。 <?php class PHPUtils { private const ERROR_CODE_TO_STRING = [ E_ERROR => 'E_ERROR', E_WARNING => 'E_WARNING', E_PARSE => 'E_PARSE', E_NOTICE => 'E_NOTICE', E_CORE_ERROR => 'E_CORE_ERROR', E_CORE_WARNING => 'E_CORE_WARNING', E_COMPILE_WARNING => 'E_COMPILE_WARNING', E_USER_ERROR => 'E_USER_ERROR', E_USER_WARNING => 'E_USER_WARNING', E_USER_NOTICE => 'E_USER_NOTICE', E_STRICT => 'E_STRICT', E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', E_DEPRECATED => 'E_DEPRECATED', E_USER_DEPRECATED => 'E_USER_DEPRECATED', ]; public static function getCurr

PHPでZipファイルを作成

PHPでZipファイルを作成するためのコードです。 ZipArchive::openメソッドの第2引数のオプションで新規作成や既存Zipファイルへの追加をコントロールできます。 ZipArchive::openメソッドの戻り値をチェックすることで、openに成功したか失敗したかを確認することができます。 function createZipFile(string $zipFilePath, $targetFiles) { $zip = new \ZipArchive(); try { $zip->open($zipFilePath, \ZipArchive::CREATE|\ZipArchive::OVERWRITE); foreach($targetFiles as $path => $localName) { $zip->addFile($path, $localName); } } finally { $zip->close(); } }

PHPの配列(array)のメモリ使用量の考察

はじめに 最近PHP上に大量のデータをメモリ上に展開していたのですが、配列(array)の形式(連想配列 or 単純な配列)や配列の要素のデータ構造(数字、配列、文字列など)で大きくメモリ使用量に差が出てくることに気づき、簡単なプログラムを組んで調べてみました。 あくまで筆者の環境での結果なので、細かい数値は参考程度に見てください。 測定環境と方法 OS: Windows 10 PHP 7.4.5 (php-7.4.5-nts-Win32-vc15-x64) 配列に要素を追加するプログラムを書いて、PHPのmemory_get_usage(true)関数を使って実メモリ使用量を計測しました。 計測結果 No. 方式 1MB当たり作成できる 要素数 プログラム 補足 1 キーも値も整数の配列 (整数IDを想定) 28571 // 2,000,000 / 70MB $row = []; for($i = 0; $i < 2000000; $i++) { $row[] = $i; } No.2~6でテストしたプログラム中の要素数は200,000。これだけ一桁多い! 2 キーが文字列、値が整数の連想配列 8333 // 200,000 / 24MB $row = []; for($i = 0; $i < 200000; $i++) { $row[$i.'_key_string'] = $i; } キーの文字列が長い方がメモリ使用量多くなる。 3 キーが整数、値が連想配列の配列 DBから取得してきたデータを想定 2325 // 200,000 / 86MB $row = []; for($i = 0; $i < 200000; $i++) { row[] = ['id' => $i]; } 4 キーが整数、値が連想配列の配列(配列に複数の値を保持) DBから取得してきたデータを想定 2127 // 200,000 /

PHPでZipファイルを解凍する方法

PHPでZipファイルを解凍するには、ZipArchiveを使うのが簡単です。パスワードの必要なZipファイルも簡単に解凍できます。 function unzipFile($source, $destination, $password=null) { $zip = new \ZipArchive(); try {   if(!$zip->open($source)) { throw new \RuntimeException('failed to open zip file: '.$source); } if($password) { $zip->setPassword($password); } $zip->extractTo($destination); } finally { $zip->close(); } }

Composerでインストールされているパッケージをバージョンチェックする方法

スクリプトで実行する場合は、-dでターゲットディレクトリを指定できる方法が楽です。 composer -d{ターゲットのディレクトリ} show 応用として、リモートホストにsshでログインして、結果をローカルのホストにファイルとして保存するbashスクリプトは、下記で実現できます。 #!/bin/bash user="ユーザー" host="ホスト" directory="対象ディレクトリ" result_file="出力結果ファイル" `ssh $user@$host "composer -d$directory show" &> $result_file'

MySQLで分散Transaction (XATrsancsaction)を使うためのPHPのサンプルコード

別サーバーにまたがる複数のDBへのTransactrionを管理するには、2 Phase Commitが用いられることがあります。 今回のコードは、できるだけ流れがわかるように可能な限り単純にしてあります。エラー処理などは厳密に実施していませんので注意してください。 その代わり1コマンドごとにログを書くようにして、エラーが起きたときにどこで落ちたか、可能な限り終えるようにしてあります。 このコードをProductionで使う場合(あまり推奨しません。。。)は、十分に検証してから使ってください。 XA Transactionのコマンドの流れ server1とserver2の2つのDBサーバーに対してXA transactionコマンドを実行する流れは、下記のようになります。 // XA trsanction start server1: XA start 'test' server2: XA start 'test' //-------------------------------- // server1とserver2のDBのデータ更新 //-------------------------------- // XA trsanction commit server1: XA END 'test' server2: XA END 'test' server1: XA PREPARE 'test' server2: XA PREPARE 'test' server1: XA COMMIT 'test' server2: XA COMMIT 'test' // 更新中にエラーが起きた場合のrollback server1: XA END 'test' server2: XA END 'test' server1: XA PREPARE 'test' server2: XA PREPARE 'test' server1: XA ROLLBACK 'test' server2: XA ROLLBACK 'test&

Symfonyフレームワークで設定されているroutingの一覧を取得する方法

Symfonyフレームワークで設定されているroutingの一覧を取得するには、下記のコマンドを実行すればOKです。 php bin/console debug:router 下記の情報が取得できます。 Name: routing名。 Method: 受け入れ可能なHTTPのメソッド。GET/POSTやANYなどが表示される。 Scheme: https、http、ANY。 Host: 普通はANY Path: URLパス。

PHPでCurlを使ってファイルをダウンロード

PHPでcurlを使ってネットワーク上からファイルをダウンロードするサンプルコードです。 用途に応じてカスタマイズして使ってみてください。 Curlの実行クラス(メモリ上に展開とファイルに直接ダウンロードするメソッド) <?php namespace Util\Net; class Curl { // 引数の$optionsで設定をカスタマイズ可能 public static function request($url, $options): CurlResponse { return self::execCrul($url, $options + [ CURLOPT_HEADER => false, CURLOPT_RETURNTRANSFER => true, // 例: 5回までリダイレクト許可 CURLOPT_FOLLOWLOCATION => true, CURLOPT_MAXREDIRS => 5, // 例: タイムアウト3秒 CURLOPT_TIMEOUT => 3, CURLOPT_CONNECTTIMEOUT=> 3, // 例: sslのエラーを無視 (安全性下がるのでよくない) CURLOPT_SSL_VERIFYHOST => false, CURLOPT_SSL_VERIFYPEER => false, ]); } // curlの実行結果を直接ファイルにダウンロードする public static function downloadAsFile($url, $storePath, $options): CurlResponse { $fp = fopen($storePath, 'w+');

PHP Symfony 1.4 Action plus View Rendering PHPUnit Test

Symfony 1.4 Action plus View Rendering PHPUnit Test I wrote test case for executing action and rendering view test. However testing action plus final rendering result is quite not easy because you need to understand how Symfony 1.4 framework handles web request and rendering model to view internally. I have investigated in the framework a bit and found a solution. Please see the test case example in the next section. Code <?php $basePath = dirname(__FILE__).'/../../../../apps/your_app_name/modules/'; $modulePaths = glob($basePath.'*', GLOB_ONLYDIR); foreach($modulePaths as $modulePath) { require_once $modulePath.'/actions/actions.class.php'; } class ActionsTest extends PHPUnit_Framework_TestCase { public function testActions() { // create stub web request $request = $this->createStubfWebRequest(); // action you would like to test. // you should pass module and action name refelctively $actions = new TargetActions($this

PHP: Execute Any DB Access Code in Transaction

Reusable Code for Execute Any DB Access Code in Transaction In this post, I will show you highly reusable utility code for wrapping and executing original db access code in transaction. Anyway let's show you the code. function executeInTransaction(callable $func) { $conn = new PDO(SERVER_NAME, USERNAME, PASSWORD, DBNAME); try { $conn->beginTransaction(); $func($conn); $conn->commit(); } catch (Exception $ex) { $conn->rollBack(); throw $ex; } } Oh, how to use? You just simply passing your core DB access code as the callable $func argument. Okay, let me show you example in next section. Example Usage First, assume you have following 2 methods for inserting data to DB. CONST SERVER_NAME = 'localhost'; CONST USERNAME = "username"; CONST PASSWORD = "password"; CONS

PHP: Process Each File in Directory

PHP Code function processEachFileIn($path, callable $callback) { $objects = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST); foreach ($objects as $object) { if (!$object->isDir() && $object->getFilename() != '.' && $object->getFilename() != '..') { $callback($object); } } } Example Usage The below code demonstrates print all file names in directory /tmp recursively. processEachFileIn('/tmp', function($file){ echo $file->getFilename()."\n"; });

Java: Joining String Array with Separator (Equilvarent to PHP implode)

If you would like to join array with a separator, what will you do? e.g. Joining "a","b","c" with separator "," to "a,b,c". In PHP, you just use implode function. Actually PHP has a lot of functions like manipulating, converting string and array. In java, these kind of functions are not provided in official JDK. If you are familiar with Java Libraries, you just use them. - Joiner in google Guava, StringUtils in Apache Coommons. I will show you quick code snippet for jonining or imploding java string array with a separator. private static String join(String[] pieces, String separator) { if(pieces.length == 0) return ""; StringBuilder sb = new StringBuilder(pieces[0]); for(int i = 1; i < pieces.length; i++) { sb.append(separator).append(pieces[i]); } return sb.toString(); } If the number of array elements is zero, the method returns empty string, but if you would not prefer this

Check Black List Words in Files by Bash Script

I wrote tiny bash script for checkin files contains black listed words or not. The aim of this script is to automate verifying php or javascript files does not have any debugging code, such as "var_dump","alert" etc. #!/bin/bash PHP_BLACK_LIST_WORDS=("var_dump(") JAVASCRIPT_BLACK_LIST_WORDS=("alert(") verify_words_are_not_contained(){ words="$1" len=${#words[@]} if [ $len -ge 0 ] then grep_words="" for word in "${words[@]}" do grep_words+="$word\|" done # remove last 2 characters grep_words=${grep_words%?} grep_words=${grep_words%?} grep_command="grep \"$grep_words\" $2 -nr --include=$3 > $OUTPUT_DIR/$4" echo "running $grep_command" eval $grep_command fi } setup_output_dir(){ target_dir_name=${1##*/} OUTPUT_DIR=$target_dir_name"_detected_black_list_words" echo "$OUTPUT_DIR" rm -r -f