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

投稿

2020の投稿を表示しています

CIサービス(アプリケーション)一覧

最近はCI用のサービス(アプリケーション)もクラウド上で提供されていたり、レポジトリサービス側から提供されていたりとずいぶん状況が変わってきました。 2012年ごろにCIの記事を書いたときに比べて格段の進化です! 今回の投稿では、最近利用できるCIサービスについてまとめてみました。随時情報は更新していきます。 名称 公式サイト 開始日 特徴 Jenkins https://www.jenkins.io/ 2011年2月20日 もともとはHudsonというプロジェクト。 オリジナルの開発者は日本人の川口耕介氏 (今もテストまわりの改善のために活躍されています!)。 歴史が長く、情報も豊富。 Pluginがとのかく豊富。 Pipeline機能があり、複雑なビルド処理もJenkinsfileを定義することにより管理できる。 Jenkinsfileには、Declarative PipelineとScripted Pipelineの2種類の書き方がある。 Teamcity https://www.jetbrains.com/teamcity/ 2006年10月2日 Jetbrains社提供。IntelliJ、PHPStorm、ReSharperなどのIDEの提供で有名な会社。 ローカルの変更をコミットせずに実行するRemote Runという機能もあり。 Bamboo https://www.atlassian.com/software/bamboo 2008年7月24日 Atlassian社提供。JiraとかConfluenceで有名な会社。 Circle CI https://circleci.com/ 2011年 クラウドとインストールバージョンの両方が提供されているが、クラウド版の方が有名。 GitLabやBitBucketなどの

GitLabのバックアップ設定

GitLabのデータのバックアップは、メインデータと設定ファイルの2つをバックアップする必要があります。 メインデータ 公式ドキュメント デフォルトのバックアップ先ディレクトリ: /var/opt/gitlab/backups /etc/gitlab/gitlab.rb内のgitlab_rails['backup_path']で変更可能 下記のようなバックアップファイルが生成される。ファイル名は、{epoch表記時間}_{年}_{月}_{日}_{バージョン}_gitlab_backup.tar -rw-------. 1 git git 33246 Dec 12 13:59 1639317570_2021_12_12_13.7.5_gitlab_backup.tar 設定ファイル 公式ドキュメント バックアップ先ディレクトリ: /etc/gitlab/config_backup 主なバックアップ対象ファイル /etc/gitlab/-secrets.json /etc/gitlab/gitlab.rb バックアップ例 -rw-------. 1 root root 267000 Dec 12 13:59 gitlab_config_1639317570_2021_12_12.tar GitLab1の公式サイトにも記載があるが、メインデータのバックアップと一緒に設定ファイルがバックアップされない理由は、設定ファイルに暗号化キーなどの機密情報が含まれているため。セキュリティ上は、バックアップも別々に保存することが好ましい。 バックアップの設定方法 バックアップ用の処理をbashにまとめてみました。 #!/bin/bash # backup gitlab main data: /var/opt/gitlab/backups gitlab-backup create STRATEGY-copy # backup gitlab config files: / etc/gitlab/config_backup giotla-ctl backup-etc # remove backup files olde

ダイソーで買った200円のドライバーセットでHDDを分解

HDDの処分 最近は個人情報の問題もあって、HDDを処分する前にちゃんとデータの消去を気にすることも多くなってきました。消去方法としては大きく分けて下記の3つがあります。 データ消去ソフトでフォーマット HDD内部のプラッタを物理破壊 データ消去を行ってくれる専門の業者や家電量販店(Sofmapやビックカメラで実施していると思います。費用発生。)に持ち込み。 データ消去ソフトでのフォーマットは簡単ですが、欠点として「フォーマットに時間がかかる」「セクタ破損などで中途半端に壊れたディスクのフォーマットができない」などがあります。 またHDD内部のプラッタの物理破壊については、HDDを分解するために、通常のプラスやマイナスドライバーではなく、星形ネジに対応したトルクスドライバーが必要とのこともあって、少し面倒です。 筆者は今回、今後もHDDの廃棄をするだろうなあと思い、思い切って自分で分解して廃棄することにチャレンジしてみました。(家電量販店に持って行くよりも安くできないかというどケチ丸出しですw) HDDの星形ネジ こんなやつです。ちなみに写真はSeagateのST2000DL003というHDDで撮影しました。 トルクスドライバー というわけで、分解のために Amazonでトルクスドライバー を探しました。 調べると T8のもだと使えそう とのことで、いろいろと物色。 セットのものとか T8一本で立派なやつとか 色々あったのですが、HDD壊すだけで800円かぁ(←どケチ)、と思って購入を躊躇。 ネット上で調べると100円ショップのダイソーでも、トルクスドライバーを販売しているとの情報をキャッチ!近所のダイソーに行って、探したところ星形のヘッド交換に対応した精密ドライバーセットがありました。 プラスが10種類、マイナスが8種類、六角が6種類、星形が6種類(今回ほしかったもの)のセットで、何とお値段税抜き200円!、税抜き200円!と安かったので、ダメもとで購入しました。 結論から言うと 買って大正解 でした。 ダイソーの精密ドライバーセット こんな商品です! 星形対応のヘッドを装着するとこんな感じ。ドライバーのグリップもゴムで滑らない様になっていて使いやす

MySQLのinsertとupdate用のPDOのPrepared Statementを生成するPHPのプログラム

PHPでMySQLのinsertとupdateで使えるPDOのprepared statementを生成するプログラムを書いてみました。使用頻度の高そうなパターンのSQLを生成することを目標にしてプログラムを書きました。 SQLBuilderクラス メインとなるクラスです。SQLBuilderクラスの内部で使っているColumnインタフェースとColumnインタフェースを実装したBindableColumn、FixedValueクラスは、SQLBuilderクラスの内部のみで使用することを想定しているので、SQLBuilder使用時に意識する必要はありません。 <?php class SQLBuilder { private string $table; /** * @var Column[] */ private array $columns = []; public function __construct($table) { $this->table = $table; } public function add($column, $type): self { $this->columns[] = new BindableColumn($column, $type); return $this; } public function addFixedValue($column, $value): self { $this->columns[] = new FixedValue($column, $value); return $this; } public function buildStatementForInsert() { $targetColumns = implode(', ', array_map(fn(Column $column) => $column->getName(),$this->columns));

複数の配列から要素を1つずつ選んで、すべての組み合わせを生成するPHPのプログラム

複数の配列から、要素を1つずつ選んですべての組み合わせを生成するプログラムをPHPで書いてみました。 ただし、組み合わせをすべて生成すると組み合わせ爆発を起こす可能性がありますので、使う際は本当に必要か慎重に検討してください。 <?php function iterateAllCombinations($sets, callable $callback) { $indexToKey = array_keys($sets); self::iterateAllCombinationsRecursive($sets, count($sets), $indexToKey, $callback, 0, []); } // 再帰的に呼ぶための関数本体 function iterateAllCombinationsRecursive($sets, $countOfSets, $indexToKey, callable $callback, $n, $generatedCombination) { if($n >= $countOfSets) { // この部分で生成された組み合わせを引数として、callbackが毎回呼ばれる。 $callback($generatedCombination); return; } $keyOfSet = $indexToKey[$n]; foreach($sets[$keyOfSet] as $e) { $generatedCombination[$keyOfSet] = $e; self::iterateAllCombinationsRecursive($sets, $countOfSets, $indexToKey, $callback, $n+1, $generatedCombination); } } 下記は、$callback内で生成された組み合わせをため込んだ場合の使用例になります。 <?php // 組み合わせを生成したい3つの配列 $sets = [ 'key1' => ['A', 'B',

PHPで呼び出されたメソッド階層を取得

デバッグ目的で、メソッドの呼び出された階層を取得したいときがあると思います。 PHPでは、Exceptionを生成してException::getTraceAsStringメソッドでStack Traceを取得する方法が簡単です。 下記にコードを示します。 <?php function extractTrace($endLineNumber, $startLineNumber=1) { $stackTrace = (new \Exception())->getTraceAsString(); $start = strpos($stackTrace, "#".$startLineNumber); $end = strpos($stackTrace, "\n#".($endLineNumber+1)); if($start !== false || $end !== false) { return substr($stackTrace, $start, ($end - $start + 1) ?: strlen($stackTrace)); } return $stackTrace; } 簡単な解説です。 引数でStack Traceを取得する範囲を指定できるようにしています。 \Exceptionを生成した個所からStack Traceが生成されるので、Stack Traceの1行目の"#0"の部分を取り除くためにデフォルトでは、$startLineNumberを1に設定しています。 $stackTrace = (new \Exception())->getTraceAsString();の部分を外部から引数で渡すのもありですが、毎回同じ処理を書く必要があるので、extractTrace関数内部に入れてあります。

PDOで指定したクラスにデータを割り当てて取得する方法

PDOで指定したクラスにデータを割り当てて取得するには、\PDO::FETCH_CLASSをMyPdo::fetch, MyPdo::fetchAllメソッドの引数に指定すれば簡単に実現できます。 通常はPDO::FETCH_ASSOCを指定して、array形式でデータを取得する方が手軽ですが、classでデータを扱うと、下記のメリットがあります。 arrayよりもclassでデータ取得した方がメモリ使用量が少ない arrayよりも、どんなデータを扱っているのかが明確になる それでは、\PDO::FETCH_CLASS使用例を下記に示します。 <?php $sql = <<<EOF SELECT id, name, weight, price FROM table_prodict EOF; $conn = new MyPdo(....); $conn->prepare($sql); $stmt = $conn->execute(); // クラスは第2引数で指定 $stmt->fetchAll(\PDO::FETCH_CLASS, Product::class); Porductクラスは下記を想定しています。 <?php class Product { public $id; public $name; public $weight; public $price; } \PDO::FETCH_CLASSの挙動についての補足です。 クラスを特に指定しないと、取得したカラムに対応したプロパティを持ったstdClassのインスタンスで結果が返ってきます。 指定したクラスに、クエリから取得したカラムに対応したプロパティがない場合(例えば、上記のProductカラムに$priceプロパティがない場合)は、動的にプロパティが定義されて取得したデータがセットされます。 \PDO::FETCH_PROPS_LATEを使うとプロパティにデータをセットする前に、クラスのコンストラクタが呼ばれます。クラスの事前処理が必要な場合に指定すると便利です。 クラスのプロパティ定義がprivateでも正しくデータはセットされます😲

PHPでarrayをgroup byする関数

通常はデータベース上でSQLを使ってgroup byをすれば十分ですが、下記のような場合プログラム側で実施するのもありです。 DBから取得したデータから別の複数の集計(group by)結果を得ることができる DBのgroup byの実行に時間がかかり、何度も似たような集計をDBに計算させるのは実行コスト(時間、CPU負荷)が高い 今回は汎用的にPHP側でgroup byを実行できるコードを書いてみました。 任意の複数フィールドで集計できるようにするため、多少コードが複雑になっています。 またkeyのencode/decode部分の処理で多少無駄があります。 <?php function groupBy(array $rows, array $groupByFields, callable $aggregate) { $groupByFieldsAsKey = array_flip($groupByFields); $map = []; foreach($rows as $row) { // キーになるならどんな関数でもOK。ここではjson_encodeを採用 $key = json_encode(array_intersect_key($row, $groupByFieldsAsKey)); $aggregatedRow = &$map[$key] ?? []; $aggregate($aggregatedRow, $row); } $result = []; foreach ($map as $key => &$aggregatedRow) { // キーをデシリアライズして、フィールドと値を集計結果の行にコピー $keyValue = json_decode($key, true); foreach($groupByFields as $groupByField) { $aggregatedRow[$groupByField] = $keyValue[$groupBy

MySQLのON DUPLICATE KEY UPDATEで複数行に対して、キーが重複しない行は挿入、キーが重複する行は新しいデータで更新する方法

MySQLのON DUPLICATE KEY UPDATEで複数行に対して、キーが存在する行は挿入、キーが重複する行は新しいデータで更新するクエリは、下記のように、VALUESキーワードを使うことでシンプルに書くことができます。 INSERT INTO table(id, score) VALUES (1, 87), (2, 75) ON DUPLICATE KEY UPDATE score = VALUES(score);

Json形式の文字列をPHP形式の配列表示に変換するプログラム

json形式の文字列をPHPの配列形式で、そのまま貼り付けて利用するためのちょっとしたコードです。 とりあえず開発中に楽をするためのプログラムなので、厳密さよりも簡便さ重視で書きました。 json形式の文字列をPHPの配列に変換 PHPの配列をJSON_PRETTY_PRINTで整形してjson形式の文字列に再変換 jsonの括弧やコロンをPHPの配列の形式に合うように変換 <?php echo strtr(json_encode(json_decode($jsonString), JSON_PRETTY_PRINT), [':' => '=>', '{' => '[', '}' => ']']);

PHPのXMLReaderを使ったXMLの読み込み

はじめに PHPでXMLを読み込むには、通常はSimpleXMLElementを使えば十分です。 ただし、XMLが巨大でメモリを節約して処理する必要がある場合は、XMLのパーサーであるXMLReaderを使って処理する方法があります。 XMLReaderを使って読み込む際はXMLの構造をどうとらえるかによって、プログラムの書き方が変わるのと、毎回読み込みの方法をプログラムしなければならないのが欠点です。 今回は下記のサンプルXMLのproduct部分を読み込むXMLReaderのプログラムのサンプルを、XMLReaderの機能紹介も兼ねて、いくつか示します。 <?xml version="1.0" encoding="UTF-8"?> <products> <date type="1">20200414</date> <product> <maker>AMD</maker> <name>Ryzen 3400G</name> </product> <product> <maker>Intel</maker> <name>Core i9 9900K</name> </product> </products> サンプルプログラムでは、下記のPHPの配列形式を取得することを目標にします。 実際の用途ではXMLReaderを使ってXMLを読み込むと同時に、CSVファイルに出力するなどといった処理が考えられます。 この場合、メモリはXMLReader部分と、読み込み途中で一時的に保持しているデータのみで利用されるので、メモリの使用量は最低限に抑えることができます。 [ [ "maker"=> "AMD", "name"=> "Ryzen

PHPで与えられた配列の次元数を取得する方法

PHPの多次元配列で次元数を推定する関数の紹介です。 [注意] 配列の最初の要素だけをチェックしていくので、すべての配列の次元が同じであること前提としています。 そもそも配列の次元数がバラバラ(C#でいうjagged array)であれば、全要素をチェックしないといけませんし、今回紹介する関数を使うということにはならないと思います。 <?php function getDimension(array $source) { if(is_array($source)) { return getDimension(reset($source)) + 1; } else { return 0; } } // 下記のような配列を引数で与えます。 $source[1]['A']['a'] = true; $source[2]['B']['a'] = true; $source[3]['A']['a'] = true; $source[4]['B']['a'] = true; // 結果は3になります。 getDimension($source);

PHPで空のディレクトリを再帰的にすべてたたどるプログラム

PHPで指定したディレクトリにある全ディレクトリ内の空ディレクトリをすべてたどるプログラムを書いてみました。 書いた動機としては、あるディレクトリ内にあるディレクトリをすべて削除したかったためです。 symbolic linkはファイルとして扱っているので無視されます。 <?php // $callbackの引数の$fileに、空ディレクトリの\SplFileInfoが渡されて呼ばれます。 function visitEmptyDirectoryRecursively($path, callable $callback) { $files = new \DirectoryIterator($path); $containsOnlyDirectory = true; /* @var $file \SplFileInfo */ foreach($files as $file) { if($file->isDot()) { continue; } else if($file->isDir()) { if(visitEmptyDirectoryRecursively($file->getRealPath(), $callback)) { $callback($file); } else { $containsOnlyDirectory = false; } } else { $containsOnlyDirectory = false; } } return $containsOnlyDirectory; } // 空ディレクトリをすべて削除する場合は、下記のようにして使います。 // permissionの関係でディレクトリを消すことができないなどのエラー処理が必要な場合は、

SQLで特定の文字を組み合わせたランダムな文字列を生成

簡易的な方法として「指定した文字列からランダムに1文字選ぶ」を必要な文字の長さ分concat関数でつなげれば実現できます。 1文字ずつ文字を選ぶので、あまり性能もよくない上、セキュリティ的な観点からのランダム性も担保されていないので、あくまで開発中に必要になった時に使う程度が無難だと思います。 下記に英数字大文字小文字を含んだランダムな3文字の文字列を生成するクエリを示します。 # RAND関数で指定した文字列からランダムに1文字選択。 # 下記の例の62の部分はa~z、A~Z、1~9の文字数の合計値を入れた結果 SELECT CONCAT( SUBSTRING('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', FLOOR(RAND() * 62 + 1), 1), SUBSTRING('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', FLOOR(RAND() * 62 + 1), 1), SUBSTRING('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', FLOOR(RAND() * 62 + 1), 1) ) AS random_string;

GitのCheckoutとCommitをするBashスクリプト

テキスト系のファイルをバックアップするために、Gitレポジトリを利用することがあるのですが、使いまわせるようにbashで簡単なスクリプトを書いてみました。 単純なバックアップ目的なので、branchは固定でmasterを指定しています。 #!/bin/bash checkout(){ local url=${1} local checkout_dir=${2} if [ ! -d $checkout_dir ] then git clone $url $checkout_dir else cd $checkout_dir git pull fi } commit(){ local commit_target_dir=${1} local message=${2} cd $commit_target_dir diff_count=$(echo `git status -s | wc -l`) if [ $diff_count -ne 0 ]; then git add -A git commit -m $message git push origin master fi } ### 使用例 ### USER=... PASSWORD=... # URL組み立て URL=https://$USER:$PASSWORD@githost/project checkout $URL "backup_directory" ### do something in backup_directory... commit "backup_directory" "Auto commit by bash" ちなみに筆者は、バックアップを下記の手順で取ってgitへ入れています。参考になれば、、、 上記のcheckout関数を用いて、gitレポジトリからプロジェクトをclone(既にclone済みの場合はpull) リモートホストからチェックアウトしたディレクトリへファイルをrsyncで同期 上記のcommit関数を用いて、gitレポジトリへpush

PHPでCSV形式でデータを出力するためのコード

PHPでCSVファイルを出力するためのコードサンプルです。 CsvWriterクラスが本体です。最低限のメソッドを定義してあるだけなので必要に応じて拡張してみてください。 Writerインタフェースを定義して、CsvWriterクラスのコンストラクタに渡して切り替えることで、出力方式を変更することができます。 CsvWriterクラス CSV形式で出力するための本体のクラスです。このクラスのコンストラクタにWriterインタフェースを実装したクラスを渡します。 <?php class CsvWriter { private Writer $writer; private $elements = []; public function __construct(Writer $writer) { $this->writer = $writer; } public function init() { $this->writer->init(); } public function appendValuesBySpecificOrder(array $values, $keys) { foreach ($keys as $key) { $this->appendEscaped($values[$key] ?? $default); } return $this; } public function appendValues(array $values) { foreach ($values as $value) { $this->appendEscaped($value); } return $this; } public function appendAsLine(array $values) { foreac

PHPでファイルを1行ごと読み込むためのIterator

タイトル通り、PHPでファイルを1行ごと読み込むためのIteratorをSplFileObjectを使って実装してみました。 <?php class TextFileRowIterator implements \Iterator { private ?\SplFileObject $file; private $filePath; private $current; private $lineNumber = 0; public function __construct($filePath) { $this->filePath = $filePath; } public function current() { return $this->current; } public function key(): \scalar { return $this->lineNumber; } public function next(): void { $this->lineNumber++; // 応用例として、SplFileObjectの呼び出すメソッドを、fgetcsvに変えるとcsvファイルを1行ごと配列で読み込むことができます。 $this->current = str_replace(["\r", "\n"], '', $this->file->fgets()); } public function rewind(): void { $this->file = null; $this->file = new \SplFileObject($this->filePath); $this->lineNumber = 0; if(!$this->file->eof()) { $this-&g

getentコマンドを使ってグループ一覧を取得

Unixのgetentコマンド(ユーザーのpasswordやgroupを調べられる)を使ってUnixでグループ一覧を取得する方法を紹介します。 # グループ一覧を表示 $ getent group ... floppy:x:19 users:x:100 ... # 特に特定のグループのグループIDを取得するには、グループ名をgrepで検索すると便利です。 $ getent group | grep group_name_you_want_to_search group_name_you_want_to_search:x:430

Linuxで新規ユーザーとグループの追加とsshでpasswordなしでログインする方法

Linuxで新規ユーザーとグループの追加とsshでpasswordなしでログインする方法のメモです。super userで実施します。 ローカルホスト側: # user1でssh keyを生成した場合の例 $ ssh-keygen -t rsa Enter file in which to save the key (/home/user1/.ssh/id_rsa): Created directory '/home/user1/.ssh'. Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/user1/.ssh/id_rsa. Your public key has been saved in /home/user1/.ssh/id_rsa.pub. ローカルホストのid_rsaはユーザーのみ読み書きできるように権限"600"を設定。 リモートホスト側: ユーザーとグループの追加 # "group1"をgroup id 100を指定して追加 $ groupadd group1 -g 100 # "user1"をuser id 200を指定して追加 $ useradd user1 -u 200 -g group1 # groupsコマンドでユーザーの所属グループを確認 $ groups user1 user1 : group1 リモートホスト側: sshでpasswordなしでログイン設定 # .sshディレクトリ作成 $ mkdir /home/user1/.ssh # [重要] .sshディレクトリの権限を変更 # "rwx------"か"rwxr-xr-x" 数字指定だと "700" か "755" でないとダメ。 $ chmod 700 /home/user1/.ssh # ログインするホストのユーザーのid_rsa.pubをauthorized_keysに追加 # 実際はssh-rsa

MySQLでデータサイズを確認する方法

MySQLでテーブルごとのデータサイズは、information_schemaから確認できます。 下記は、テーブルごとのデータサイズとインデックスサイズの合計を取得するためのクエリ例です。 SELECT table_schema, table_name, ROUND((data_length + index_length) / 1024 / 1024), 2) AS `MB` FROM information_schema.Tables ORDER BY (data_length + index_length) DESC;

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 => '

Linuxで特定のパターンにマッチするファイルの削除方法

Linuxで特定のパターンにマッチするファイルの削除方法について簡単にメモ 例1: /tmpディレクトリ配下の128bit hash(例:e111c876b32143abccd98bb56542bcc2)のパターンにマッチするファイルをすべて削除 regextypeを指定しないと我々が普段(?)よく知っているposixのregexが使えないことに注意! find /tmp -type f -regextype posix-egrep -regex '\./[0-9a-f]{32}' -delete 例2: /tmpディレクトリ配下の、AAAで始まるディレクトリで、更新日が5日以前のものを、ディレクトリの中身のファイルも含めてすべて削除 最後の「-exec {} +」は複数のファイルをまとめてコマンド実行するという意味 「-exec {} ;」の場合はファイル一個ずつの処理になるので、rmの場合は「-exec {} +」の方が効率が良い。 find /tmp -mindepth 1 -maxdepth -type d -mtime +5 -name ".AAA*" -exec rm -rf {} +

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

Bashでオプション引数をとる方法

下記の例で、変数A, B, Cにbashからオプション引数を割り当てる方法示します。 #!/bin/bash set -e set -o pipefail OPT=`getopt a:b:c: $*` set -- $OPT for i; do case $i in --) shift; break ;; -a) A=$2; shift; shift;; -b) B=$2; shift; shift;; -c) C=$2; shift; shift;; esac done # check if A, B and C are properly given from option. if [ -z "$A" ]; then echo "A is not specified"; exit; fi if [ -z "$B" ]; then echo "B is not specified"; exit; fi if [ -z "$C" ]; then echo "C is not specified"; exit; fi

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のようです。

MySQLでサブクエリで先に集計してクエリの実行を高速化

サブクエリを用いたクエリの最適化 巨大なテーブルをFull Table Scanして件数を集計する場合に、JOINの少ないサブクエリで先に集計して後から他のテーブルをJOINすると、クエリの実行を高速化できる場合があります。 特にJOINするテーブルが多い時には実行時間に大きな差が出ます。 もちろん、データやINDEXの張り方にも依存するで、クエリを書き換える前にEXPLAINで必ずRows_examinedなどが減ることを確認することは必須です。 この記事では、huge_rows_tableに大量の行があり、他のテーブルの行数はそれほどない場合を想定したクエリ例を示します。 想定するテーブル CREATE TABLE `huge_rows_table` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `group_id_1` bigint(20) NOT NULL, `group_id_2` bigint(20) NOT NULL, `check_flg` tinyint(4) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `IDX_group_id_1` (`group_id_1`), KEY `IDX_group_id_2` (`group_id_2`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ; CREATE TABLE `group_table_1` ( `id` bigint(20) NOT NULL, `name` varchar(500) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ; CREATE TABLE `group_table_2` ( `id` bigint(20) NOT NULL, `name` varchar(500) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ; クエリ # 元クエリ SELECT gt1.name AS g

Linuxで特定のディスクの使用率を調べるコマンド

Linuxで特定のディスクの使用率は下記のコマンドで調べられます。 df {対象のディレクトリパス} | awk '{print $5}' | sed -ne 2p | cut -d"%" -f1 応用で、Bashを使って下記の機能を実現させてみます。 対象ディレクトリを指定できるように 制限値より多い場合に実行失敗にする #!/bin/bash checkDiskUsage() { local $directory=$1 local $limit=$2 # 下記のコマンドの前にssh hostのようにすれば、リモートホストでも実行可能。 local used=`df $directory | awk '{print $5}' | sed -ne 2p | cut -d"%" -f1` echo "Disk usage of $directory: $used%" if [ $used -gt $3 ] then echo "Exceed limit of disk usage (greather than $limit%)" exit 1 fi } checkDiskUsage "/tmp" 80

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(); } }

Linuxでテキストファイルから重複行を取り除く方法

下記のコマンドで実現できます。 less {対象のテキストファイル} | sort -s -k 1 | uniq > result.txt コマンドの簡単な解説です。 lessコマンドで、対象のテキストファイルを読み込み sortコマンドで、1行目を指定してソート uniqコマンドで、重複を取り除く 結果をテキストに書き込み

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&

Linuxコマンド1行で、ファイルが空かどうか判定してメッセージ出力

もっとスマートなやり方があるかもしれませんが、下記のコマンドで実現できます。 du -h {調べたい対象のファイル} | awk -F ' ' '{if (\$1 == 0) print "file is empty"; else print "file is not empty";}' duコマンドの結果でファイルサイズを取得 (ファイルサイズ取れればどんなコマンドでもOK) コマンド出力結果でファイルサイズの部分が0かどうかをawkで判定して、ファイルサイズの結果に応じてメッセージを出力

zip圧縮されたsqlを解凍しながらMySQLに実行させる方法

mysqldumpなどで生成されたzip圧縮された巨大なsqlを解凍しながら、mysqlへ実行(リストア)する方法です。 パイプ(|)とリダイレクト(<)を駆使します。 gunzip < test.sql.gz | mysql -u USER -pPASSWORD -D DBNAME ちなみにmysqldumpの出力をzip圧縮する方法は下記になります。 mysqldump -u USER -pPASSWORD DBNAME | gzip > dump.sql.gz

Eclipseでコードカバレッジのハイライトを削除する方法

Eclipseには便利なコードカバレッジ表示機能が搭載されていますが、コード内に緑、赤、黄の色付けがされて煩く感じるときもあると思います。 1度カバレッジの色付けが出てしまった後に消す方法の紹介です(方法は簡単)。 下記のキャプチャの青いマーカーで示した「Remove All Sessions」のボタンを押せばすべて消えます。

MySQLのユーザーの全権限をsqlで取得する方法

MySQLのユーザーの権限は「show grants for user@host」のようにコマンドを実行すれば取得できます。 すべてのユーザーを取得する場合は、mysql.userテーブルからユーザーとホストを取得し、concat関数を使って、全ユーザー分のshow grantsコマンドをsqlとして取得すると楽です。 下記に例を示します。 # 全ユーザー分のshow grants実行コマンドをsqlファイルに出力 mysql -N -e 'SELECT CONCAT("SHOW GRANTS FOR `", user, "`@`", host, "`;") FROM mysql.user order by user, host' > /tmp/show_grants_for_all_users.sql; # 生成したsqlファイルをmysqlで実行させて、あとはlinuxのコマンドを駆使して加工 # 下記は、取得したGRANT文をREVOKEに変換し、権限を削除するためのsqlを生成する例になります。 cat /tmp/show_grants_for_all_users.sql | mysql -N |egrep 'what you would like to extract' | grep -v USAGE | sed -e "s/GRANT/REVOKE/g" -e "s/$/;/g" -e "s/TO/FROM/g" > /tmp/mysql_revoke_example.sql

Java 3Dの開発環境の構築 (2020年3月版)

Java 3D 大昔に作ったJava3Dのアプリケーションを復活させようと悪戦苦闘しているのですが、 最近(といっても2012年)にJava3DはOpenGLベースに移行して、細々と開発が続いていることがわかりました。 https://gouessej.wordpress.com/2012/08/01/java-3d-est-de-retour-java-3d-is-back/ https://jogamp.org/wiki/index.php/Java3D_FAQ (参考) いまだに残っているoracleのページのJava 3Dのバージョンは1.5.1です。 筆者はWindows 10環境ではインストール試していません。 https://www.oracle.com/technetwork/java/javase/tech/index-jsp-138252.html Java 3Dの開発環境の構築 https://jogamp.org/deployment/java3d/  のダウンロードページからjarなどを落としてもよかったのですが、mavenでプロジェクト管理しているので、多少古いバージョンの1.6.0.1をpom.xmlに追記して対応しました。 https://mvnrepository.com/artifact/com.massisframework.j3d/java3d-core https://mvnrepository.com/artifact/com.massisframework.j3d/vecmath <!-- https://mvnrepository.com/artifact/com.massisframework.j3d/vecmath --> <dependency> <groupid>com.massisframework.j3d</groupid> <artifactid>vecmath</artifactid> <version>1.6.0.1</version> </dependency> <!-- https://mvnrepository.com/a

MySQLでトランザクションを長時間掴んだままのプロセスを強制終了させる方法

下記のようなコマンドで実行できます。{}で囲った部分は実際の運用に応じて値をあてこんでください。 mysql -u{user} -p{password} -e "SELECT CONCAT('kill ', pl.id, ';') FROM information_schema.processlist AS pl INNER JOIN information_schema.innodb_trx AS trx ON trx.trx_mysql_thread_id = pl.id WHERE pl.time > {time_to_kill};" |sed -e '1,1d' | mysql -u{user} -p{pass} 上記のコマンドの簡単な解説です。 information_schemaのprocesslistテーブルとトランザクションを管理しているinnodb_trxテーブルをjoinして、{time_to_kill}秒以上実行されているプロセスを特定し、特定したプロセスをkillするsqlコマンドを生成 mysql -u{user} -p{password} -e "SELECT CONCAT('kill ', pl.id, ';') FROM information_schema.processlist AS pl INNER JOIN information_schema.innodb_trx AS trx ON trx.trx_mysql_thread_id = pl.id WHERE pl.time > {time_to_kill};" 生成された行の1行目はヘッダーなので削除(sedコマンドを使って、1行目から1行目までを削除)。 sed -e '1,1d' 最後の部分で、生成されたsqlをmysqlに流し込んで実行させる。

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パス。

Gitでコミットログ内のコミッター名やメールアドレスの変更

別のパソコンで、新たにレポジトリをクローンしてコミットしたところ、設定を間違えて、別にユーザー名とメールアドレスでコミットしてしまいました。 間違ってしまった過去のコミットログ内の、ユーザー名とメールアドレスの修正を試みました。 下記のGitHubの記事の通り実施したらできました。 https://help.github.com/en/github/using-git/changing-author-info Windowsでgitをインストールすると使える、git bashを使って無事Windows環境で修正できました。 メールアドレスをキーにして検索して置換していく方法のようです。

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+');