目当てのIPアドレスを探せ

ある日、ネットワーク内につないだ機器のIPアドレスが現地で分からなくなったので、調べてほしいと依頼がありました。当日は力技で何とかしましたが、もっと楽にできることあったので、振り返ってみます。

前提条件

幸いなことに、今回問題のネットワークでは、以下の点が分かっていました。
  • 現地の機器をつないでいるネットワークへアクセスできるサーバーに、本社オフィスからアクセスできた。
  • 探すべきIPアドレス帯は第3オクテットまで固定、かつ第4オクテットの範囲が決まっている。
  • IPアドレスを指定してwgetが成功することで、そのIPが当たりと言える。
これなら何とかなりそうです。

当日の手段:力業

当日は仕方なかったので、こんなシェルスクリプトを書いて、見つかったらCtrl+Cで対応しました。
wget -t 1 -T 3 --spider http://X.X.X.1/hogehoge;
wget -t 1 -T 3 --spider http://X.X.X.2/hogehoge;
・・・
wget -t 1 -T 3 --spider http://X.X.X.99/hogehoge;
wget -t 1 -T 3 --spider http://X.X.X.100/hogehoge;
IP1個につき1回wgetし、3秒待機するコマンドを第4オクテットとして考えられる範囲分(上の例なら100行分)を書いています。URLの存在チェックができれば十分だったので、–spiderを付けました。最初から1行ずつ実行していくだけのコマンドです。 コマンド自体は表計算ソフトで量産できますが、都度作るのは煩雑です。一発でできるようにしたいです。

次回からはこれを使う:ワンライナー

xargsを使うことで、ワンライナーで実行できます。
$ seq 1 100 | xargs -I@ wget -t 1 -T 3 --spider http://X.X.X.@/hogehoge;
seqコマンドで生成された数を、xargs以下に渡しています。X.X.X.@ のアットマーク分がxargsの引数で変化し順次実行していきます。これで目標とした検索が実現できます。 1から順に確認して見つけたらCtrl+Cで止めるのは、力業と同じです。

Ctrl+Cを使わない方法はあるか

Ctrl+Cで止めない方法も考えたいと思って、for文でコマンドを試作してみました。
## 1から順番に数を調べて、3の倍数になったらループを抜ける例。
for v in `seq 1 10`; do if [ $((v % 3)) -eq 0 ]; then echo fuga; break; else echo $v; fi; done
実際書いてみると、単語が込み入って見づらいと感じました。これなら、素直にシェルスクリプトファイルにした方が見やすそうです。よりよい方法があるかもしれませんが、今回はこれ以上の調査を見送ります。

余談

「ワンライナー」という言葉を使いましたが、似た感じの言葉で「リニア」があります。「ライナー」と「リニア」の違いが気になって調べてみました。
  • ライナー(liner)
    • 定期運行する交通機関
    • 「京成スカイライナー」「おはようライナー」の「ライナー」はこちらの意味
  • リニア(linear)
    • 一直線に伸びた感じの意味
    • リニアモーターカー linear motor car
    • 線形代数学 linear algebra

C#でTesseract 5

矢野です。
社内向けに作成していたC#アプリケーションで利用するTesseractのバージョンを4系から5系に更新した概要を簡単にメモしておきます。Tesseractに限らずC#での画像認識に関する情報って、Pythonなどに比べると日本語でも英語でもかなり少ないですよね。ビジネスロジックをつくっていく段階であれば、もちろん他のプログラミング言語向けの情報でも大いに参考になるのですけれど、最初の一歩して「とりあえず動くようにする」ところがその言語なり、ライブラリなりの固有の手順が必要で、そういうところで引っ掛かりがちです。 .NET Framework, .NET向けに作成されたTesseractのラッパーライブラリとしてよく使われているのは恐らくcharlesw/tesseractだと思います。私もこれまでお世話になってきていたのですが、残念ながらTesseract 4.1までしかサポートされていません。今すぐ5系が必要な状況というわけではないですが、余力のある時に乗り換えておいた方が良いこともあるだろうと思って、対応したライブラリを探してみました。 上記のcharleswさんのところから派生したSicos1977/TesseractOCRを導入することで、ほんの少しのコードの修正でTesseract 4系から5系へ移行することができました。
NuGetパッケージマネージャーからTesseractOCRをインストール
Visual Studioからであれば、NuGetパッケージとして簡単にインストールできます。 以降前のcharlesw/tesseract向けのコードは、非常に簡素に書くとこんな感じ。

using Tesseract;

// 中略

// PictureBoxに表示
pictureBox1.Image = new Bitmap("image\\lipsum.png");

// 文字認識
var engine = new TesseractEngine("tessdataフォルダのパス", "eng");
using (var pix = Pix.LoadFromFile("image\\lipsum.png"))
{
    var page = engine.Process(pix);

    // 結果表示
    textBox1.Text = page.GetText();
}


これを、Sicos1977/TesseractOCR向けに修正すると、

using TesseractOCR;

// 中略

// PictureBoxに表示
pictureBox1.Image = new Bitmap("image\\lipsum.png");

// 文字認識
var engine = new Engine("tessdataフォルダのパス", "eng");
using (var pix = TesseractOCR.Pix.Image.LoadFromFile("image\\lipsum.png"))
{
    var page = engine.Process(pix);

    // 結果表示
    textBox1.Text = page.Text;
 }


名前空間とクラス名、オブジェクトの構成を少し修正するだけで、ほぼそのまま使えました。
分かりにくいけど上が認識対象の画像、下がTesseract 5.2での認識結果です。
えっ? たったこれだけ? そうなんです、たったこれだけでした。その先は、これまでのTesseract 4系向けにつくっていたのと同じようにビジネスロジックをつくり込んでいくことができました(同時期にEAST text detectorを組み込んでみたりもしたけど、それは別の話)。これまで未経験の技術とか、「たかが」ライブラリのアップデートとか、どうしても気後れしちゃうこともありますが、一歩踏み出してしまえば「たったこれだけ」で進めちゃうこともよくあるよね、というお話でした。

illustratorからSVGを出力してHTMLに表示する

株式会社イメージ・マジックの技術ブログ、今回の担当のsoenoです。
最近SVGを触ることがあり、いろいろできるんだなあという印象です。
手軽に使えるということでボタンをillustratorからSVGを書き出して実際に使うまでの流れを紹介します。

illustratorからSVGを書き出す

  1. illustratorでボタンを作成し、ボタンの要素をグループ化します。(要素を選択し右クリック)



  2. グループ化した要素をもう一度右クリックし、書き出し用に追加(単一のアセット)します。



  3. アセットの書き出しタブの中の書き出し設定のセレクトボックスでSVGを指定します。



  4. アセットの書き出しパネルの右下のボタンから書き出しを選択。


  5. 書き出したい場所を選んで保存。

HTMLに埋め込む

HTMLでイメージの参照先に保存したSVGを指定する(下のパスは同一階層にある場合の指定)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>svg追加</title>
</head>
<body>
    <img src="btn.svg" width="79" height="35">
</body>
</html> 


上のコードをブラウザで表示させた時

最後に

上の例ではillustratorから書き出したSVGをHTMLに組み込む場合の流れを書きました。
illustratorからの書き出し方にもSVGの埋め込み方にも他の方法があります。
表示方法でできることが変わったりもします。
(SVGをHTMLに埋め込めばjavascriptから見た目を変更できる等)
また、SVG自体も使うプロパティーでいろいろな表現ができます。
プロパティーはブラウザの対応状況にばらつきがあるので使用時には少し注意がいりそうです。
自サイトのブラウザの対応範囲と、代替え手法が用意できるかなど、
様子見しながら使う感じになるのではないかと思います。

IllustratorのVer UPによるPNG読込挙動について

はじめに

こんにちは、イメージ・マジックのもあいです。
弊社では印刷用データの自動処理でPhotoshopやIllustratorのスクリプトを使用する事があるのですが、最近でIllustratorのバージョンアップを行ったら正常に動作しなくなったと言うことがありました。
その事象について備忘録として記事を残しておくことにします。

事象

具体的にどうなったのかというと、Illustratorに配置している画像がずれていてかつ大きさが小さくなるという事象でした。 報告を受けて使用しているIllustratorのバージョンを報告者に確認したところ26.1.0/26.2.1は問題が発生しないが、26.5だと発生するとのことでした。こういう事象が発生したときにバージョンを確認するとよくあるのが「最新版」という報告ですが、それが無かっただけでも良かったと思っています。
ただ、それでも不明なバージョンもあったので、Adobe Creative Cloudでバージョンダウングレードテストも行って、結果としては26.2まではOKで26.3/26.4/26.5はNGでした。

原因

Illustrator上で画像が小さくなったとのことでしたので、最初から解像度の問題であることを疑いましたし、それが正解でした。サーバ上で解像度(DPI値)のみを埋め込んでいたのですが、単位までは埋め込んでいなかったのが原因で、それによってIllustratorのバージョンによって解釈が異なっていることがわかりました。 Illustratorは解像度を埋め込んでいない画像については72dpiとして扱います。26.2までは解像度のみを埋め込んでも72dpi扱いで、単位を埋め込んで初めて認識します。26.3からは解像度のみを埋め込むと単位をdpiで認識していました。これが原因して今まで動いていたものが動かなくなりました。

検証

5種類の画像を用意してIllustratorの26.2と26.5に読み込ませたらどうなるかを検証しました。
下記のような5種類の画像を用意しました。
No 解像度 単位 大きさ(px) 備考
1 なし なし 800 x 80 何も埋め込んでいないバーコード画像
2 なし なし 610 x 88 1に余白をつけたバーコード画像
3 200 なし 610 x 88 1に余白をつけて解像度を付与
4 200 PixelPerInch 610 x 88 1に余白をつけて解像度/単位を付与
5 72 PixelPerInch 610 x 88 1に余白をつけて解像度/単位を付与
下記の画像ですが上記の表の画像を上から順にIllustrator 26.2.1に読み込ませました。
No4は200dpiとして認識しているので小さく表示されていますが、それ以外は一緒です。   次にIllustrator26.5で読み込ませた結果です。
No4はバージョンが異なっても同じですが、No3の解像度のみ指定している画像も200dpi扱いになって小さく表示されます。 ※上記2つの画像は同じ拡大率で表示してるものを縦に並べてスクリーンショットをとっています。

結論

画像の解像度を埋め込む場合は、解像度とともに単位も設定する。片方だけだと誤動作するので厳密に値を設定するのがよい。

ImageMagickで服の色を変える

こんにちは岡野です。
現在開発中のプロジェクトで、服の色を変更する処理を実装したので紹介します(簡易的な方法です)。

環境

ImageMagick 6.9.12-59 Q8

手順

1. 元となる画像を用意します。例:Tシャツ ホワイト。
2. 例えばネイビーへ変更するとして変更前後の色についてRGBそれぞれの差分を計算します。
ホワイト: rgb(255, 255, 255)
ネイビー: rgb(32, 47, 85)
R: 32 - 255 = -223
G: 47 - 255 = -208
B: 85 - 255 = -170
3. ImageMagickのevaluate Addに差分の値を渡して色を変換します(Q16環境では各値を256倍します)。
convert src.png \
-channel R -evaluate Add -223 \
-channel G -evaluate Add -208 \
-channel B -evaluate Add -170 \
dest.png

まとめ

ImageMagickだけでも結構良い感じになりました。将来はAIで着色したいと思います。

その他

最近Zabbixでハマったことです。グラフ内の文字が出力されないため悩んでいたのですがopen_basedirの設定不足でした(zabbix_server.logに気をとられNginxのログに気づくのが遅れた)。何か問題があった際は落ち着かないとダメだと感じました。

SymfonyとVue.jsを組み合わせるときはSymfony Formを使うのは諦めた方が良さそう

初めまして。イメージ・マジックのスズキです。
いきなりですが、本稿は『SymfonyとVue.jsを組み合わせるときは「Symfony Forms」の機能を使うのは諦めた方がいいかもしれない』ということを伝えたい、ただそれだけの記事です。『エンジニアたる者、どんなときでもまずは結論から話せ』と、物心ついた頃(嘘)から聞かされてきましたしね。
Symfonyで一般的なデータの入力や保存を行う画面をつくるなら、「Symfony Forms」という機能を利用するのが基本のようです。
公式ドキュメントでも、基本的な実装方法として紹介されています。

Symfony Formsを使うことで、コントローラー側でフォームと画面項目の定義をしておいて、画面側(テンプレート側)では自力でタグを書いたりせずに表示することができます。
試しに、テキスト項目が2つあるだけの画面を作る場合、こういう感じになるでしょうか。
// エンティティ
namespace App\Entity;

class Master
{
    private string $name = '';
    private string $description = '';

    public function getName(): string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;
        return $this;
    }

    public function getDescription(): string
    {
        return $this->description;
    }

    public function setDescription(string $description): self
    {
        $this->description = $description;
        return $this;
    }
}
// フォーム
namespace App\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

class MasterEditType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('name', TextType::class)
            ->add('desctiption', TextType::class)
        ;
    }
}
// コントローラー
namespace App\Controller;

use App\Entity\Master;
use App\Form\Type\MasterEditType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class MasterEditController extends AbstractController
    public function edit(Request $request) : Response
    {
        $master = new Master();
        // ...
        
        $form = $this->createForm(MasterEditType::class, $master);

        return $this->render('master/edit.html.twig', [
            'form' => $form->createView(),
        ]);
    }
}
<!-- 画面テンプレート(Symfony Form使用) -->
<div>
    <div>
        <label>名称</label>
        {{ form_widget(form.name) }}
    </div>
    <div>
        <label>説明</label>
        {{ form_widget(form.description) }}
    </div>
</div>
画面のテンプレートには <input type="text"> といったごく普通のHTMLタグが登場しません。Symfony formsがタグを生成してくれるのですね。

ただ、リッチなフロント画面をVue.jsでごりごり書きたいときは、<input type="text" v-model="master.name">などとVue.jsのディレクティブを入れることになるのですが、Symfony Formsを使っているとそれがしにくくなります。
そんなわけで、Vue.jsでいろいろやりたいならば、Symfonyは使ったとしてもSymfony Formsの機能は使わない方が良さそう、といった印象です。

Symfony Formsを使わずに書くなら、サーバ側ではSymfony Formの代わりに単純なオブジェクトを作ってリターンし、画面側ではVue.jsのお作法でそれを表示するような作りが良さそうです。
先ほどのコードを、Symfony Formsを使わずに(Vue.jsを使えるように)書き直すと、こんな感じでしょうか。
// 変更後のコントローラー(Symfony Formを使わない)
namespace App\Controller;

use App\Entity\Master;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class MasterEditController extends AbstractController
    public function edit(Request $request) : Response
    {
        $master = new Master();
        // ...

        return $this->render('master/edit.html.twig', [
            'master' => [
                'name' => $master->getName(),
                'description' => $master->getDescription(),
            ],
        ]);
    }
}
// 変更後の画面テンプレート(Vue.js使用)
<div>
    <div>
        <label>名称</label>
        <input type="text" v-model="mst.name">
    </div>
    <div>
        <label>説明</label>
        <input type="text" v-model="mst.description">
    </div>
</div>
<!-- Vue.jsのスクリプト部分 -->
<script>
export default {
  props: {
    master: {},
  },
  setup(props) {
    const mst = ref(props.master);
    return {
      mst,
    };
  },
}
</script>
ということで今回は、SymfonyとVue.jsを組み合わせるときのお話でした。Symfony単体やVue.js単体での使い方は情報があっても、組み合わせ方の情報はなかなか無いので書いてみました。
Web界隈はフレームワークやライブラリがたくさんあって、組み合わせるときは試行錯誤が要りますね。

PHP触ったことのないプログラマーが動的な型付けに頭を抱えた話


こんにちは!5月に入社した黒羽(くろは)です!


隔週での更新を目標に開発一同で再開しましたテックブログ、再開後としては2つ目(?)の記事になります。
いきなり社歴の浅い新人が書いているので驚かれる方もいると信じて簡単な自己紹介をしつつ、本題に入っていこうと思います。 
さて、文系の学部を出てプログラマーになって6年目も中盤に差し掛かっておりますが、大学在学中よりも時間の経過が速かった気がします。
今までに取り扱った言語としては以下のような感じです。
  • Java
  • C#
  • Python
  • javascript
  • CSS
  • HTML
  • その他にコマンドプロンプト(世の人々がプログラムと聞いてイメージするであろう『黒い画面に文字がダーッとでる』やつ)で使用するスクリプト類など…
  最後のは言語じゃなくてコマンドの塊じゃん!と言われそう イメージマジックの開発で主に使われている言語はPHPなので入社して初めてPHPを触りました。
今まで触ってきた言語とは書き味や仕様が違っていて日々調べながら習得を進めています。
今日はそんな言語仕様に振り回されてハマってしまった時の原因について調べたので(社内ではそりゃ当たり前だろといわれそうなので未来のPHP初学者に向けて)共有できればと思います。

きっかけ


イメージマジックの各プロジェクトのPHPのソースコードを眺めているとどのソースにも
declare(strict_types=1);
と出てきます。 これは何ぞやと調べてみたところ次の記事に行き当たりました。
この記事の冒頭でこのように記述されていました。
 
 
declare(strict_types=1); とは、PHP7から導入された、厳格な型検査モードの指定構文です。

厳格な型検査モード・・・・?????と思いつつ読み進めてみると更に以下の記述が。
<?php
function add(int $a, int $b): int
{
    return $a + $b;
}

var_dump(add(1.0, 2.0));
この状態で単体実行すると、int(3)が出力されます。
  この時の私の頭の中では1つの考えに支配されていました。
「int型の変数にdouble型を堂々と代入するな!!!!!!」


言語による型付け


あまりに理解ができなくて大声になりましたが気を取り直していきます。
プログラミング言語には開発者が変数や戻り値になど事前にどういう型のデータが入るかを指定する静的な型付けと実行時にコンパイラやインタプリンタがコードを解釈してよしなにデータ型を判断してくれる動的型付けがあります。
PHPは言語仕様として動的な型付けを行っています。
私はJava()静的な型付け)からプログラミングをはじめて、当時の学習用テキストにもデータ型が一致しないとエラー吐くと脅されて教えられました。
そのため、データ型が定義されている変数に異なるデータ型を突っ込まれると困ってしまうわけです。
しかもdouble同士の加算でint型で返ってくるとはこれ如何に。
上記のサンプル関数では$a=1.0≒1(int型)、$b=2.0≒2(int型)としてPHPの方で判断しているようです。
話は逸れましたがdeclare(strict_types=1);を付けてさきほどの関数を実行すると

PHP Fatal error: Uncaught TypeError: Argument 1 passed to add() must be of the type integer, float given, called in /Users/hiraku/sandbox/stricttypes/A.php on line 9 and defined in /Users/hiraku/sandbox/stricttypes/A.php:4 Stack trace: #0 /Users/hiraku/sandbox/stricttypes/A.php(9): add(1, 2) #1 {main} thrown in /Users/hiraku/sandbox/stricttypes/A.php on line 4

とエラーが発生します。
つまりdeclare(strict_types=1);とは動的な型付けをしているPHPで静的な型付けのように振舞わせる宣言という風に解釈できます。
ちなみにこの宣言をしているファイルを呼び出している場合のみ有効であるため、別ファイルからuse文等で参照して同じように関数を呼び出しても緩い型検査のままであるのでint型で返ってきます。
つまり引数を$a=1.0,$b=2.0とする場合、
  • declare(strict_types=1); を宣言しているファイルAのadd関数を呼び出す⇒Typeエラーが発生
  • declare(strict_types=1); を宣言しているファイルAを参照しているがdeclare(strict_types=1); を宣言していないファイルBでファイルAのadd関数を呼び出す⇒int型の戻り値が返却される。 
ということです。

最後に


つまるところ何が言いたかったのかというとカルチャーショックを受けたという話でした。
動的な型付けは手軽な一方で想定していない値が返ってくる可能性があるのでやはり多少面倒でも静的な型付けできっちりとデータ型を指定して書く方が私は好きです。(というかぱっと見で何の値が入ってくるのかソースから読み取れないと後々困ることもある)
最後に余談となりますがこの記事を書いてる間に裏どりがてらに色々調べていたのですが 「動的な型付け」と「型推論」は同じものだと思っていましたが違うもののようです。
ここら辺も少し調べてみようと思います。
ここまで読んでいただきありがとうございました。何かの参考になれば幸いです!


LaravelのCollectionパッケージでPHPの配列操作を劇的に快適にする

こんにちは、池田です。最近家の鍵をsesame4というスマートロックにしました。鍵の向きを考えずにスマートフォンをかざすだけでドアが開くというのは、スマートフォンの充電端子がType-Cになったときと、駅の改札を交通系ICで通れるようになったときを合わせたような感動があります。まあどっちも昔のことで大して覚えていないので、適当言ってるだけなんですが。

本題

さて、皆さんはこの世で最も邪悪なものを知っていますか。そう、PHPでの配列操作のやりにくさですね。どれだけ配列操作がやりにくいかについては、以下の記事を読むのが良いと思います。
PHPを使いもせずDISってる君達へ 
特に記事のPHP Tips 5 : array_filterが歯抜けになるのに気をつけようの現象には、
自分「よし、配列をフィルタして最初の値を取ろう!」
array_filter([1,2,3], fn(int $num) => $num > 1)[0]
PHP「だめだよ」
自分「???」
理由:array_filterされた配列は[1 => 2, 2 => 3]になっており、0のキーは存在しないので取れないから
と何度も苦しめられました。さすがに学習した今でもこの挙動に関しては納得できず、思い出す度にFワードが出そうになります。会社のテックブログじゃなければ出してます。
また、連続で配列操作関数を使用した際のネストの深さ、読みにくさもピカイチです。こちらは先程の記事で一番最初に紹介されてますね。
これは Symfony という素晴らしいフレームワークを使っていても、如何ともし難いものがありました。先程の記事にもあったように、Symfonyでは配列をラップしたDoctrineの Collectionクラス が存在するんですが、メソッドの数が痒い所に手が届かない感じで(ソートができない!)(フラットにできない!)(ユニークにできない!)、複雑な配列操作をしようとするなら一度ネイティブの配列に戻すことが必要になります。なんか気持ち悪いですね。継承してオレオレ実装もあまりしたくありません。絶対バグを生み出すので。
ならばどうすれば良いのか……?ここで本題です。LaravelのCollectionパッケージを使いましょう。

何が良いのか

公式ページ にアクセスして、メソッド一覧を見てください。たくさんのメソッドありますね。131個あるらしいです。これだけ数があるということは、ニーズにあったメソッドも存在する可能性が高いということです。
例えば、先程挙げた3個の「ソート」「フラット」「ユニーク」は全て用意されたメソッドで可能です。また、無駄にネストを深くする必要もありません。
collect([1, [2, 3, [4, 2]]])
  ->flatten()
  ->unique()
  ->sort();
上記のように、非常にシンプルな記述をすることが可能です。
中でも、私がイチオシのメソッドは mapWithKeys です。皆さんも幼稚園くらいの頃に「以下のように連想配列に変換したいな……」と考えたことがあると思います。
↓これを
[
    [
        'name' => 'John',
        'department' => 'Sales',
        'email' => 'john@example.com',
    ],
    [
        'name' => 'Jane',
        'department' => 'Marketing',
        'email' => 'jane@example.com',
    ]
]
↓こうする
[
    'john@example.com' => 'John',
    'jane@example.com' => 'Jane',
]
ネイティブの配列操作関数でやるなら、恐らくarray_reduceでやる必要があると思います。ですが読みにくくなるので、使わないに越したことはありません。LaravelのCollectionを使えば以下のように簡単に変換することができます。
$collection = collect([
    [
        'name' => 'John',
        'department' => 'Sales',
        'email' => 'john@example.com',
    ],
    [
        'name' => 'Jane',
        'department' => 'Marketing',
        'email' => 'jane@example.com',
    ]
]);

$keyed = $collection->mapWithKeys(function ($item, $key) {
    return [$item['email'] => $item['name']];
});

$keyed->all();

/*
    [
        'john@example.com' => 'John',
        'jane@example.com' => 'Jane',
    ]
*/
別の言語だとJavaScriptは結構配列操作がやりやすいと思っているんですが、それでもこの変換をここまでシンプルにはできなかったと思うので、mapWithKeysには驚きました。 しかし、中にはLaravelを使っていない人もいるでしょう。Laravelのパッケージをまるごと入れてCollectionだけ使うしかないのでしょうか。当然そんなことはありません。以下のコマンドでこれだけ(+α)導入可能です。
composer require illuminate/support
これにより、Symfony環境であってもLaravelのCollectionの素晴らしさを享受することができます。
……え、フレームワーク使ってない?Composerなんてない?……それは……その……どうすればいいんでしょうね……?

まとめ

  • PHPネイティブの配列操作はやりにくい
  • LaravelのCollectionパッケージを使えば途端にやりやすくなる
  • 単体(+α)導入も可能

Symfonyで2要素認証

こんにちは、岡野です。
最近Symfonyを使っているWebサイトで2要素認証を実現しました。その際に使用したscheb/2faバンドルは非常に使いやすく、Symfony公式マニュアルを見ながら容易に実現できました。但し一点問題があり、認証コードを入力しないままユーザが画面遷移すると中途半端な認証状態になってしまうので困りました。
今回はその回避方法を共有します。
 

環境

Symfony: 4.4.20
scheb/2fa-*: 5.13.1
 

起きていた現象

1. ユーザがID/PWを入力。
2. 認証コードを尋ねるポップアップを表示。
3. ユーザが他の画面へ遷移。

→ 2.でSymfony内部の認証状態が「Partially authenticated」になるため、3.でController#getUser()すると認証が完了していないユーザエンティティが取得できてしまう(※1)。
また3.のページがfirewallで制限している場合は認証コードを尋ねるページが表示される。一般的な通常サイトの様にログイン画面へ遷移させたい(※2)。
 

※1(完全認証前のユーザが取得できてしまう)の回避方法

EventListenerを使って、認証コードの確認が完了するまでuserをanonymousにする。
 
class AuthAttemptListener implements EventSubscriberInterface
{
  public static function getSubscribedEvents(): array
  {
      return [
          TwoFactorAuthenticationEvents::ATTEMPT => ['onTwoFactorAuthAttempt'],
          TwoFactorAuthenticationEvents::COMPLETE => ['onTwoFactorAuthComplete'],
          TwoFactorAuthenticationEvents::FAILURE => ['onTwoFactorAuthFailure'],
          KernelEvents::RESPONSE => ['onKernelResponse'],
          KernelEvents::TERMINATE => ['onKernelTerminate'],
      ];
    }

  /**
   * セッション内の情報が正しければ、バンドル内の2要素認証処理用にトークンへユーザを設定する。
* ユーザは後で解除する。
   */
  public function onTwoFactorAuthAttempt(TwoFactorAuthenticationEvent $event): void
  {
      // ...
      $this->attemptingToken->setUser($user);
    }

  /**
   * 2要素認証が成功した場合、トークンを認証状態に設定する。
   */
  public function onTwoFactorAuthComplete(TwoFactorAuthenticationEvent $event): void
  {
      // ...
      $this->attemptingToken->getAuthenticatedToken()->setAuthenticated(true);
    }

  /**
   * 2要素認証失敗時、トークンのユーザを元に戻す
   */
  public function onTwoFactorAuthFailure(TwoFactorAuthenticationEvent $event): void
  {
      if ($this->attemptingToken) {
          $this->revertTokenUser();
      }
    }

  /**
   * 例外発生時、トークンのユーザを元に戻す
   */
  public function onKernelResponse(): void
  {
      if ($this->attemptingToken) {
          $this->revertTokenUser();
      }
    }

  /**
   * 安全のためSymfonyの処理が終わる際に、万が一処理中のトークンが残っていたら
* 例外を発生させてエラー通知する。
   */
  public function onKernelTerminate(): void
  {
      if ($this->attemptingToken) {
          throw new \LogicException();
      }
    }

  private function revertTokenUser(): void
  {
      $this->attemptingToken->setUser('anon.');
      $this->attemptingToken->getAuthenticatedToken()->setAuthenticated(false);
      $this->attemptingToken = null;
  }
}

※2(認証コード要求ページが表示される)の回避方法

認証コードが要求された場合、ログイン画面へ遷移する。
 
services.yaml
  app_2fa_required_handler:
        class: App\Security\TwoFactorAuth\AuthRequiredHandler

class AuthRequiredHandler implements AuthenticationRequiredHandlerInterface
{
  public function onAuthenticationRequired(Request $request, TokenInterface $token): Response
  {
      $this->tokenStorage->setToken(new AnonymousToken('', 'anon.'));
      throw new AccessDeniedException();
  }
}
参考になれば幸いです。

同じ名前で中身がちがうもの

こんにちは。イメージマジック三浦です。 最近めっきり寒くなってきました。体調には十分に気を付けてください。今日は「同じ名前で中身がちがうもの」と題して書いてみます。

アニメの場合

アニメ作品では、異なる登場人物を同じ声優が担当することがあります。 最近一番驚いたのは、アニメ「ルパン三世」の次元大介役の声優交代の話です(本投稿執筆時点の約3か月前にインターネット記事で出ています)。交代前後の声優は以下の通りです。
  • 交代前:小林清志さん
  • 交代後:大塚明夫さん
お二人はOVA「機動戦士ガンダム0083 STARDUST MEMORY」にて、「デラーズ・フリート」という陣営の上官と部下として共演されています。
  • 小林清志さん:エギーユ・デラーズ
  • 大塚明夫さん:アナベル・ガトー
記事を見て 「次元大介の声が デラーズからガトーへ 受け継がれるか」と感じました。 「デラーズからガトーへ」というキーワードでインターネット検索すると、この声優交代の情報が出てくるところに反響の大きさを感じました。 劇中、2人の登場人物には深い信頼関係があったので、熱い気分になりました。

PowerShellの場合

話はPowerShellに変わります。こちらは、はまりポイントになったという話です。

きっかけ

Windows11がWindows Updateから利用できるようになったのを受けて、自分の作業用PCをWindows11にアップグレードしました。 そのPCで自社開発アプリの動作確認を実施していましたが、アプリの1つでPowerShellスクリプトが動かないエラーが出ました。
このシステムではスクリプトの実行が無効になっているため、ファイル XXXX を読み込むことができません。
PowerShellの実行権限が無いときに出るエラーメッセージです。

調査してみる

実行権限の不足ということで、普段使っているPowerShellでLocalMachineに「RemoteSigned」を設定してみたが動きません。
PS C:\> Get-ExecutionPolicy LocalMachine
RemoteSigned
PS C:\>
OS再起動しても動きません。 アプリを開発したメンバーにも確認しましたが「PowerShell」の権限設定に問題はなさそうでした。 Windows11ではPowerShellを起動するときに何か特殊な対応が必要なのか?と思って一度は諦めました。

謎が解けた

後日、アプリのログファイルに「PowerShell.exe」と出ているのを見つけて、自分のPCに2種類のPowerShellがいることに気づきました。

2種類の「PowerShell」

2種類のPowerShellとは以下の通りです。
  • Window10プリインストール版
    • PowerShell5.1
    • プロセス名は「powershell.exe」
  • 普段使っているPowerShell(後でインストールした後継版)
    • PowerShell7.2
    • プロセス名は「pwsh.exe」
「powershell.exe」 と「pwsh.exe」は、それぞれでスクリプト実行権限を管理していて、一方で権限を開放しても他方に反映されません。 「普段使っているPowerShell」=「pwsh.exe」で権限を開放しても、「powershell.exe」には一切効果なし。そして 「powershell.exe」 がスクリプト実行権限を開放していなかったので、
このシステムではスクリプトの実行が無効になっているため、ファイル XXXX を読み込むことができません。 
が延々と出ていました。実際に「powershell.exe」で実行権限を開放したら、無事に動きました。

「謎」の正体

今回の謎の正体は「PowerShell違い」でした。幸いにして特殊な対応は不要でした。

2つのPowerShellの見分け方

最後に2つのPowerShellの見分け方をご紹介します。
※本記事執筆当時、三浦の作業用PCで確認した動作です。Windows UpdateやPC本体の設定により異なる可能性があります。

起動直後のメッセージ

  • powershell.exe は「新機能と改善のために最新の PowerShell をインストールしてください!」とメッセージを表示します。
  • pwsh.exe は「PowerShell 7.2.0」のように、バージョン番号を先頭行に表示します。

バージョン番号

このコマンドでPowerShellのバージョン番号が分かります。
$PSVersionTable | find --% "PSVersion"
powershell.exe
PS C:\> $PSVersionTable | find --% "PSVersion"
PSVersion                      5.1.22000.282
PS C:\>
pwsh.exe
PS C:\> $PSVersionTable | find --% "PSVersion"
PSVersion                      7.2.0
PS C:\>

最後に

PowerShell5.1, PowerShell7.2, どちらも「PowerShell」と呼んで間違いではないので、混乱したという話でした。
「PowerShell」と呼ばれる異なるバージョンのアプリが同居している可能性を常に考えておきたい、と思った出来事でした。 本日は以上です。 2022.06.02 誤字脱字を訂正しました。