日本国内からのアクセスを識別するときに、IPv4の頃はリモートホスト名が/\.jp$/であるかや、nifty.comみたいな例外を個別で見てやる程度で何とかなっていたが、IPv6は逆引きができないので、それをどうにか解決する方法。
確認環境
| Env | Ver |
|---|---|
| PHP | 8.3.8 |
| geoip2/geoip2 | 3.2.0 |
やり方
- ComposerでGeoIp2を入れる
php composer.phar require geoip2/geoip2 - dbipからIP to City Lite databaseのMMDBを落としてくる。
https://download.db-ip.com/free/dbip-city-lite-2025-11.mmdb.gzのURL形式なのでYYYYとMMのとこをいじっても落とせる。 こんな感じにコードを書く
<?php require 'vendor/autoload.php'; use GeoIp2\Database\Reader; // 落としてきたMMDBを指定 $cityDbReader = new Reader('dbip-city-lite-2025-11.mmdb'); // IPを渡すと結果が取れる $record = $cityDbReader->city('2403:6200:8860:e103:a4b2:ff66:9e68:6436'); print($record->country->isoCode . "\n"); // 'US' print($record->country->name . "\n"); // 'United States' print($record->country->names['zh-CN'] . "\n"); // '美国' print($record->mostSpecificSubdivision->name . "\n"); // 'Minnesota' print($record->mostSpecificSubdivision->isoCode . "\n"); // 'MN' print($record->city->name . "\n"); // 'Minneapolis' print($record->postal->code . "\n"); // '55455' print($record->location->latitude . "\n"); // 44.9733 print($record->location->longitude . "\n"); // -93.2323 print($record->traits->network . "\n"); // '128.101.101.101/32'
参考
- GeoIP extension Introduction - www.php.net
- GeoIP2 PHP API - maxmind.github.io
あとがき
国情報を利用してアクセスを弾く必要があったので雑に調べて導入したが、dbipの無料DBは正確性が低いので、個人の小規模利用向けだと思う。
エンタープライズ用途などで制度が必要な場合は有料DBの契約を検討したほうがいいだろう。
しかしGeoIp2はPHPのマニュアルにあるからPHPに組み込みで存在してるのかと思ったら、まさかのComposerで驚いた。PHPも変わったなぁ…とかしみじみ。
でも昔からPearはあったような気もするし、今更か…?
こんな感じにフォーカスが当たるとOS標準の枠が出て、矢印キーの左右で選択状態を切り替えられる<input type="radio" />要素の作り方。
大まかには<label><input type="radio" /></label>の構造にして、UI上はラベルを疑似要素的に表示し、ラジオボタン本体はUI上見えなくするが、display: none;にせず、画面に残すことによってタブ遷移できるフォーカス可能要素として設計する内容。
サンプルコード
CSS
/* ラジオボタンを等間隔に整列し、左右に余計な空白を持たせない */
.radio-container {
display: flex;
column-gap: 0.25rem;
}
.hidden-radio {
/* チェックを消す */
appearance: none;
/* 非推奨要素を使っているが、iOS Safari対策 */
position: absolute;
clip: rect(0, 0, 0, 0);
pointer-events: none;
/* デフォルトマージンがチェックボックスの位置にあるので消す */
margin: 0;
}
.radio-label {
/* 文字列要素を綺麗に中央寄せする */
display: flex;
justify-content: center;
align-items: center;
/* 枠装飾、基本的にフォーカス枠が角丸であり、違和感がないようにborderも角丸にしておく */
padding: 5px;
border: 1px solid #60bce9;
border-radius: 4px;
/* 見かけ上ボタンなのでカーソルをボタン用にする */
cursor: pointer;
}
/* ラジオチェック時にラベルの背景色を変化させる */
.radio-label:has(.hidden-radio:checked) {
background-color: #60bce9;
}
/* ラジオフォーカス時にラベルのフォーカス枠を出す */
.radio-label:has(.hidden-radio:checked):focus-within {
outline: auto;
}
HTML
<form>
<div class="radio-container">
<label class="radio-label">
<input name="r2" type="radio" class="hidden-radio" value="1" checked />
<span>道明寺</span>
</label>
<label class="radio-label">
<input name="r2" type="radio" class="hidden-radio" value="2" />
<span>長命寺</span>
</label>
</div>
</form>
Windowsに入っている古くて使いづらいPowerShellをアップグレードするところから始める。
- Powershell 7.xを入れる
- PowerShellを管理者権限で起動
Set-ExecutionPolicy RemoteSignedを流す- hoge.ps1などの名前で適当にファイルを作り、
C:\Program Files\PowerShell\7\pwsh.exeに関連付けする
自動生成されたHTMLから社員情報を引っこ抜くスクリプトを作った時に考えたことを書き出してみる。ブラウザのDevToolsを使った簡易ツールを書いていて、頭に浮かんだことだ。データやコードは架空の内容なので実在しない。
ベースのHTML
大まかに最初に目に入ってきたのはこんな内容だった。機械生成されているせいかフォーマットがガタガタ気味だ。
<span class="Content">
<span id="undefined">SHAIN.XLS page1</span>
</span>
<span class="Content">
<br>
<span id="undefined">社員リスト</span>
<span id="undefined"></span>
</span>
<span class="Content">
<br>
<span id="hoge1">00001</span>
<span id="fuga1">:山菱喜一</span>
</span>
<span class="Content">
<br>
<span id="hoge2">00002</span>
<span id="fuga2">:鈴木与一</span>
</span>
<span class="Content">
<br>
<span id="hoge3">00011</span>
<span id="fuga3">:茨城妙子</span>
</span>
<span class="Content">
<span id="undefined">SHAIN.XLS</span>
<span id="undefined"> page2</span>
</span>
<span class="Content">
<br>
<span id="hoge4">00012</span>
<span id="fuga4">:奈良楓</span>
</span>
...
まずは関数ベースで考える
ひとまず普段は関数ベースで実装しているので関数ベースで実装する。書いていてgetEmployee(name, list)の仮引数にあるlistは冗長ではないかということに気づく。
const emp_list = document.getElementsByClassName('Content');
[...emp_list].reduce((acc, cur) => {
if (cur.id === 'undefined' || cur.children.length !== 3) {
return acc;
} else {
const id = cur.children[1].innerText;
const name = cur.children[2].innerText.replace(/^:/, '');
if (/^\d+$/.test(id)) {
acc.push({ id, name });
}
return acc;
}
}, []);
const getEmployee = (name, list) => {
const re = new RegExp(`.*${name}.*`);
const result = list.filter((emp) => re.test(emp));
console.log(result);
};
クラスにしてみる
そしてクラスにしたほうが素直にならないかと考え、クラスにしてみた。getEmployee(name, list)はsearch(name)となり、冗長性が減ったほか、抽象的な表現で分かりやすくなった気がする。
class EmployeeSearch {
constructor() {
const emp_list = document.getElementsByClassName('Content');
this.list = [...emp_list].reduce((acc, cur) => {
if (cur.id === 'undefined' || cur.children.length !== 3) {
return acc;
} else {
const id = cur.children[1].innerText;
const name = cur.children[2].innerText.replace(/^:/, '');
if (/^\d+$/.test(id)) {
acc.push({ id, name });
}
return acc;
}
}, []);
}
search(name) {
const re = new RegExp(`.*${name}.*`);
const result = this.list.filter((emp) => re.test(emp.name));
console.log(result);
}
}
追加仕様に合わせ関数を新設し、クラスを改良する
動作確認をしていく中で表示と名前が分かれたパターンを発見したので、これに合わせて直してゆく。
<span class="Content">
<br>
<span id="hoge2">00002</span>
<span id="fuga2">:飯田</span>
<span id="piyo2">太郎</span>
</span>
この対応をしようとすると分岐が必要だと考えたが、愚直にやってしまうとコンストラクタの中がごちゃつく上、テストもしづらいと考え、DOMをパースする処理を外部関数に切り出し、それを呼ぶことにした。
const getEmployeeFromList = (domList) => {
const id = domList[1].innerText;
const name1 = domList[2].innerText.replace(/^:/, '');
if (domList.length === 3) {
return {
id,
name: name1
}
} else {
const name2 = domList[3].innerText;
return {
id,
name: `${name1}${name2}`
}
}
};
class EmployeeSearch {
constructor() {
const emp_list = document.getElementsByClassName('Content');
this.list = [...emp_list].reduce((acc, cur) => {
if (cur.id === 'undefined' || cur.children.length < 3) {
return acc;
} else {
const { id, name } = getEmployeeFromList(cur.children);
if (/^\d+$/.test(id)) {
acc.push({ id, name });
}
return acc;
}
}, []);
}
search(name) {
const re = new RegExp(`.*${name}.*`);
const result = this.list.filter((emp) => re.test(emp.name));
console.log(result);
}
}
更により良くするには?
ブラウザのDevToolsにあるConsoleで書いたコードなので、現実問題テストコードも書かないし、そこまで凝る必要もないのが、よりよい設計にするにはコンストラクタでパースせず、事前にパース処理した結果をコンストラクタを通じて注入して処理するようにするとよいだろう。そうすると責務が分離され、テストがしやすく、コードもきれいになると考える。
クラスにするか、関数にするか
これはまだ正直答えが見えていないが、インスタンスを引き回す場合はクラスのほうが都合がいい可能性がある気がしてきている。
関数でもできるにはできるが、毎回引数にインスタンス相当のものを渡すのは冗長である可能性がある。疎結合という文脈では関数のほうがより良い可能性もあるが、人間の認知負荷や、保守性との兼ね合いだと思う。
明確な損益分岐点はまだ見えていないし、今回のケースだと割とどっちでもいい部分もあるとは思う。
InterfaceDescriptionの取り方(Get-NetAdapterだけでは見切れる)
Get-NetAdapter | Format-List -Property Name,InterfaceDescription
Shell Scriptとは思想が違うためif ('Up' -eq ($adaptor | Format-List -Property status))では上手くいかない
while ($true) {
$adaptor=Get-NetAdapter -InterfaceDescription "Intel(R) Ethernet Connection XXX" -ErrorAction SilentlyContinue
if ($adaptor -eq $null) {
Write-Host 'イーサネット3のStatus: UPを待機しています...'
Start-Sleep -Seconds 5
} else if ('Up' -eq $adaptor.status) {
Write-Host 'イーサネット3の疎通が確認できたため、Hogeを起動します...'
Start-Process -FilePath 'C:/path/to/Hoge.exe'
break
} else {
Write-Host 'イーサネット3のStatus: UPを待機しています...'
Start-Sleep -Seconds 5
}
}