Composer の autoloader のハッシュ値はなに?

vendor/autoload.php にある、 ComposerAutoloaderInitXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX の XXX... のことね。

手抜きでサーバ側に Composer 入れず autoloader だけ入れたいなと思ったんだけど、なにか環境ベースの値だったらいやなので調べた。

<?php
...
$suffix = $config->get('autoloader-suffix') ?: md5(uniqid('', true));

composer/AutoloadGenerator.php at master · composer/composer · GitHub

らしい。

uniqid の第2引数指定してるのが気になるけど、まあ多分安全じゃないかな。

しかしほんとブログ長いこと書いていなかった。書きたいネタ自体はいろいろあったんだけど。少しリハビリしなきゃな……。

PHPではグローバル変数は閉じ込めることができる。そして、関連して引っかかったこと

前提として、PHPでは変数には2つのスコープしかない。グローバルと、各関数(メソッド)ごとのものだ。ブロックスコープがないのが不便とはいえ、グローバル変数を使わなければ割と平和だ。

しかしレガシーコードを扱う場合など、仕方なくグローバル変数を使わざるをえない場合はある。今回、あるファイルに並んだグローバル変数が設定として使われている例があった。

ぜんぶ書き換えられるなら書き換えたいところだったが、なかなかそうも行かない。仕方ないので、せめて新しいコードからはラッパクラス経由でアクセスすることにした。

以下のようにすればベストだったと思われる。

<?php
# legacy-config.php

$xxx = 'OK';
<?php
# lib/MyApp/Config.php

namespace MyApp;

class Config
{
    public function get_xxx() {
        require 'legacy-config.php';

        return $xxx;
    }
}

require は、実行されたコード上のその位置でそのまま実行されたようにふるまう(おそらく)。そのため、 $xxx は、 get_xxx() のスコープになる。

こうすればグローバル変数のはずのものを、関数のスコープに閉じ込めることができて安全である。が、そのことを知らずによくない実装をしてしまっていた。

<?php
# lib/MyApp/Config.php

namespace MyApp;

require 'legacy-config.php';

class Config
{
    public function get_xxx() {
        global $xxx;

        return $xxx;
    }
}

こちらの実装でも、グローバル変数が漏れてはいるものの、動作自体は変わらない。

そのためしばらくは問題なかったのだが、あるとき設定の取得ができなくなった。調べてみると、 get_xxx() がなにも返していない。

原因はオートローダを導入したことだった。オートローダを導入するとなぜグローバル変数が消えるのか。さらに調べて、最初の「グローバル変数を関数に閉じ込めることができる」仕様を知った。

PHPのオートローダの実装は、 spl_autoload_register() という組み込み関数によって実現される。

クラスが必要とされた際に、この spl_autoload_register() の第1引数に設定した関数がコールバックされて、そこで呼び出すという形だ。たとえば最小限(かつ適当)なオートローダの実装はこんな感じになる。

<?php

spl_autoload_register(function ($class) {
    require strtr($class, '\\', '/') . '.php';
});

new \NS\Cls(); # NS/Cls.php が自動的に require される

見ての通り、 require は関数内で行われている。そのため、

  • オートローダで読み込まれたコード内のあらゆるグローバル変数は関数スコープになる。
  • さらに、オートローダで読み込まれたコード内で読み込まれたコードも関数スコープになる。

対策としては、今回のような場合は最初のコードのように、そもそも関数(メソッド)内で require すればよい。

なお require_once にするとまた別の問題が起きるので注意。すでにほかで読み込まれているときにわかりづらく問題になる。

また、どうしてもグローバル変数グローバル変数そのままで必要な場合は、そこだけ手動で読み込めばいいだろう。

Microsoft アカウントの登録がいろいろ微妙

Visual Studioのライセンス認証のために必要となったので登録しようと思ったが、いろいろ微妙だった。

URLがlive.comのまま。

ホーム|Microsoft アカウント から登録しようとしたのだが、「旧 Windows Live ID」とかあるのにlive.comのままで、バグってるのかと思った。

登録時に既存のメールアドレスで登録するか、Hotmailの新規アカウントを作って登録するかの選択肢があるが、違いがわからない。

以下を参考にした感じ、後者だと電話番号がいるように見えたが、じっさいにそうなのかは不明。

なぜかというと、じっさいに既存のメールアドレスで登録しようとしてみても電話番号フォームがあり、しかし入力は不要だったからだ。

ほんと謎。意味不明。

パスワードが16文字まで。

それ自体がもう言うことなしだが、さらに、16文字以上突っ込んだときのエラーが「17字以上は駄目です」な感じで無駄にわかりづらいという。


まあ目的を達するのに問題があるってほどじゃなかったけど、これだけリソースのある会社がこの程度のことしかできてないの見ると、残念な気持ちになる。

Firebugなど各種開発ツールで、input:textのvalueの変化が取れない

<!DOCTYPE html>
<html>
  <head>
    <script src="//code.jquery.com/jquery-1.11.3.min.js"></script>
    <script>
jQuery(function ($) {
    $('input:text').val('ok');
    $('input:hidden').val('ok');
});
    </script>
  </head>
  <body>
    <form>
      <input type="text" name="text" value="">
      <input type="hidden" name="hidden" value="">
      <input type="submit">
    </form>
  </body>
</html>

こんな感じでJavaScriptからvalueを設定したとき、input:hiddenの変化はFirebugの方から取得できるのだが、なぜかinput:textの方が駄目だった。

f:id:xxmagai:20150720140843p:plain

この状態でsubmitするとクエリはちゃんと、 text=ok&hidden=ok となる。Firebugの表示だけおかしいのだろう。

なお、Firebug以外でも、IE, Firefox, Chromeの各開発ツールでも同じようになった。HTMLの仕様によるものだろうか。

知らずに引っかからなくていいところで引っかかりそうになった。危ない。

Windowsフォームデザイナーでツールボックスからカスタムコントロールを追加する場合の注意点

Visual Studio Community 2013で、C#で、Windows Formsでアプリ書いているんだが、なかなかやっかいなはまり方することが多くて困る。

今日はListViewを、継承してカスタムしたクラスに置き換えようとしてはまった。

カスタムコントロールを作るまでは、MSDNに書いてある感じでまあ問題ない。

方法 : 既存の Windows フォーム コントロールから継承する

問題はこのカスタムコントロールを、フォームデザイナーを使って、ツールボックスから追加しようとした場合に起きる。

上記の手順で追加した場合、カスタムコントロールはWindowsフォーム本体と同じ名前空間に属する。それ自体は自然だし問題ない。

だがフォームデザイナーがどうも、コントロールとフォーム本体が同じ名前空間に属する場合を想定していないようで、おかしなコードを出力する。

namespace MyApp
{
    partial class MyApp
    {
        ...

        private void InitializeComponent()
        {
            this.listView1 = new MyApp.ListView();

            ...
        }

        ...
    }
}

このコードが生成される、 myapp.designer.cs ファイルの名前空間MyApp になっているので、コンパイラMyApp.MyApp.ListView を見ようとしてエラーになる。

型名 'ListView' は型 'MyApp.MyApp' に存在しません。

対策としては、手動で書き換えるしかなさそうだ……。

今は大丈夫だけど、これからコントロールが増えてきたらどうしよう。

  • フォームデザイナーなんて使わず、手動で書く。
  • WPFに移行する。

どっちがいいだろうかね。困った。

おまけ

カスタムコントロールの方のクラスはまあこんな感じになるわけだが、

namespace MyApp
{
    public partial class ListView : System.Windows.Forms.ListView
    {
        public ListView()
        {
            InitializeComponent();
        }
    }
}

間違ってこう書くと、名前が被ってエラーになる。

using System.Windows.Forms;

namespace MyApp
{
    public partial class ListView : ListView
    {
        public ListView()
        {
            InitializeComponent();
        }
    }
}
'MyApp.ListView' と 'MyApp.ListView' を含む、循環する基本クラスの依存関係です。

まあ、ちゃんと文法わかってればない間違えなんだけど。

追記。

どうも、フォームデザイナーで修正する度に書き換えられちゃう模様。本気でどうにかしないと……。

C#のcatchがちょっと面白い

最近C#をちょっと書いているのだが、まったく未知のところからやっているとつい変なコードを書いてしまう。

先日は、こんなコードを書いた。

using System; // for Exception

class Program
{
    private static void Main() {
        try {
            throw new Exception("error!");
        }
        catch (Exception) {
        }
    }
}

見ての通り、catch句に受け取る型名しか書いていない。なんでこんなことが起きるのかとTwitterでつぶやいたら、こんな感じで教えていただいた。

実用的には、あるクラスの例外だけ再スローして、ほかの例外は処理する、などに使えるのだろう。

using System;

class ExceptionA : Exception
{
    // 関係ないけどコンストラクタいちいち書かないといけないのなんとかならんの?
    public ExceptionA(string message) : base(message) {
    }
}

class Program
{
    private static void Main() {
        try {
            throw new ExceptionA("error!");
        }
        catch (ExceptionA) {
            // こうすると再スローできる
            throw;
        }
        catch (Exception e) {
            // ExceptionA以外のExceptionクラスの例外は、処理する
            Console.Write(e);
        }
        // こうすると残りぜんぶ取れる
        catch {
        }
    }
}

型のゆるい言語を使っていると、catchした後にクラスを見て処理とか考えてしまうが、型の厳しい言語ではこういうやり方があるのだろう。

型の厳しい言語はほとんど使ったことがないので勉強になる。

追記

タイトルをちょっと修正した。メソッドじゃないのにcatch()とかおかしかったので。

メソッドといえば、メソッドのパラメータにクラス・型だけ書くのはできないようだ。まあ、意味ないしね。

// コンパイルすると、
// ... error CS1001: ID がありません。
// となる。
using System;

class Program
{
    private static void Main() {
    }

    private void Method(Exception) {
    }
}

QuickRunでC#叩けるようにする……までもなかった話

QuickRunはC#デフォルトで対応していた

なにを間違ったかC#書いてて、基本はIDEでいいのだけど、ちょっとしたことを試すのにQuickRunが使いたくなったのであちこち検索して設定してみた。

のだが、さらに調べるとそもそもQuickRunは現在デフォルトでC#に対応していた。

最初動かなくて気付かなかったがそれはPATHを設定していなかっただけだった。のでPATHを設定。

PATHには、C:\Windows\Microsoft.NET\Framework64\v4.0.30319 とか、csc.exe がある場所を指定すればいい。

だが出力が文字化けた。調べてみると、termencoding が空だった。set encoding=utf8 していなければそれでも大丈夫だった気がするが、してたのでまあ、termencoding も設定。

set termencoding=cp932

手動設定を試していたときに :@" ではまった件

この間のKindleのセールで買ったVimテクニックバイブルをちびちび進めていて、その中にあった、Vimスクリプトをコピーして :@" で実行、というテクニックを最近使っていたのだが、

let g:quickrun_config = {
            \ ...

みたいなスクリプトでエラー (E15) が出る。

:h :@ で調べてみたところ、 :@{register} での実行は、Exコマンドを連続実行してるだけみたいな感じで、どうも行の継続は無理っぽい。

対処法が思い付かなかったが、QuickRunはVimスクリプトも当然対応しているので変なことせずにQuickRun使えば大丈夫だった。

QuickRunマジ便利という話。