k-holyのPHPとか諸々メモ

Webで働くk-holyがPHP(スクリプト言語)とか諸々のことをメモしていきます。ソースコードはだいたいWindowsで動かしてます。

Windows7+VirtualBox+Vagrant再入門

Vagrantは2013年の記事 Windows7にVirtualBoxとVagrantをインストールしたメモ でインストールはしてみたものの、日々の開発作業に追われるまま長らく使っていませんでした。

実はこのブログでは一番人気の記事で、言及リンクしていただいた数も最も多いのですが、書いた私自身はほぼそれっきりでVagrant使ってなかったという…。(ノ∀`)

ローカル開発で常用するには重くて厳しいし、そもそも担当者は自分だけで管理してるサーバも片手で数えられる程、root権限貰って一から構築できる機会もない、といった理由で学習コストに見合う利点がまだ感じられなかったのです。

(自分自身がそうなんだから、上司にとってはもっとそうだろうなぁと…なかなかこういう開発環境構築、みたいな作業に何日も使うのも難しいんですよね。)

しかし、今回再び新規サーバ環境を構築機会ができ、そろそろ一人担当からも卒業しなければ、という流れにもなってきましたので、再び入門してみたメモです。

以下、手順は途中まで前の記事そのままですが、Windowsで開発中のApache + MySQL + PHPアプリケーションのテスト環境構築をAnsibleで自動化するというのが真の目標なので、とりあえずVagrantBoxのインストールと起動、Vagrantfileの編集までに留めます。

また、ゲストOSからホストOSの所属するLAN上のデータベースを利用したいので、プライベートネットワークを有効にします。

VirtualBoxを入れる

Downloads - Oracle VM VirtualBox から VirtualBox 4.3.26 for Windows hosts x86/amd64 をダウンロードして実行。

Setup Wizardが起動するので Next Next。なんかOracle製のドライバ類が色々インストールされるけど気にしない。

Vagrantを入れる

Download Vagrant - Vagrant から WINDOWS Universal (32 and 64-bit) をダウンロードして実行。

Setup Wizardが起動するので Next Next。インストールが終わると「再起動しろ」と言われるので、再起動します。

Vagrant Boxファイルを入れる

A list of base boxes for Vagrant - Vagrantbox.es から、使いたい仮想サーバのイメージファイルを選択して、Vagrantに追加します。

今回も使い慣れたCentOS6系で CentOS 6.4 i386 Minimal (VirtualBox Guest Additions 4.3.2, Chef 11.8.0, Puppet 3.3.1) を選択。

以下、コマンドはNYAOSでホームディレクトリにて実行。

ホームディレクトリにてVagrant boxを追加

$ vagrant box add centos64_i386 http://developer.nrel.gov/downloads/vagrant-boxes/CentOS-6.4-i386-v20131103.box
==> box: Adding box 'centos64_i386' (v0) for provider:
    box: Downloading: http://developer.nrel.gov/downloads/vagrant-boxes/CentOS-6.4-i386-v20131103.box
    box: Progress: 100% (Rate: 9639k/s, Estimated time remaining: --:--:--)
==> box: Successfully added box 'centos64_i386' (v0) for 'virtualbox'!

以前と比べて、進捗が分かりやすく表示されています。

'centos64_i386' (v0) と表示されているのは、今のVagrantではboxファイルのバージョン管理を行ってくれるためのようです。

Vagrantが管理しているboxは追加の際に付けた名前でディレクトリが作成され、例えば今回の場合は ~/.vagrant.d/boxes/centos64_i386/0/virtualbox 以下にファイルが配置されます。間の 0 がバージョン番号になるんでしょうか。

プロジェクトのVagrant設定

Vagrantの設定も含めてプロジェクト単位でgitのバージョン管理下に入れるため、以後はプロジェクトディレクトリ内で行います。

プロジェクト名は "delivery" としました。

$ cd ~/Documents/Projects/delivery
$ mkdir vagrant
$ cd vagrant
$ vagrant init centos64_i386
A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.

vagrant init コマンドを実行した場所にVagrantfileが作成されました。

ひとまず準備できたので起動してみます。

$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'centos64_i386'...
==> default: Matching MAC address for NAT networking...
==> default: Setting the name of the VM: vagrant_default_1430453933460_60304
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
==> default: Forwarding ports...
    default: 22 => 2222 (adapter 1)
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2222
    default: SSH username: vagrant
    default: SSH auth method: private key
    default: Warning: Connection timeout. Retrying...
    default: Warning: Connection timeout. Retrying...
    default: Warning: Remote connection disconnect. Retrying...
    default:
    default: Vagrant insecure key detected. Vagrant will automatically replace
    default: this with a newly generated keypair for better security.
    default:
    default: Inserting generated public key within guest...
    default: Removing insecure key from the guest if its present...
    default: Key inserted! Disconnecting and reconnecting using new SSH key...
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
==> default: Mounting shared folders...
    default: /vagrant => C:/Users/k_horii/Documents/Projects/delivery/vagrant

以前はPageant(PuTTY authentication agent)を起動していると怒られたんですが、改善されたみたいですね。これは嬉しい。

$ vagrant ssh
Welcome to your Vagrant-built virtual machine.
[vagrant@localhost ~]$ pwd
/home/vagrant

無事に入れました。

[vagrant@localhost ~]$ sudo vi /etc/sysconfig/network
NETWORKING=yes
HOSTNAME=localhost.localdomain

以前はPuTTY経由でないとできなかったファイルの編集も、できるようになってます。

Vagrantのプライベートネットワークを利用する

プライベートネットワークを利用して、ゲストOSからホストOSが所属するLANにアクセスできるようにします。

Vagranfileの内容を確認してみます。

# -*- mode: ruby -*-
# vi: set ft=ruby :

# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure(2) do |config|
  # The most common configuration options are documented and commented below.
  # For a complete reference, please see the online documentation at
  # https://docs.vagrantup.com.

  # Every Vagrant development environment requires a box. You can search for
  # boxes at https://atlas.hashicorp.com/search.
  config.vm.box = "centos64_i386"

  # Disable automatic box update checking. If you disable this, then
  # boxes will only be checked for updates when the user runs
  # `vagrant box outdated`. This is not recommended.
  # config.vm.box_check_update = false

  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine. In the example below,
  # accessing "localhost:8080" will access port 80 on the guest machine.
  # config.vm.network "forwarded_port", guest: 80, host: 8080

  # Create a private network, which allows host-only access to the machine
  # using a specific IP.
  # config.vm.network "private_network", ip: "192.168.33.10"

  # Create a public network, which generally matched to bridged network.
  # Bridged networks make the machine appear as another physical device on
  # your network.
  # config.vm.network "public_network"

  # Share an additional folder to the guest VM. The first argument is
  # the path on the host to the actual folder. The second argument is
  # the path on the guest to mount the folder. And the optional third
  # argument is a set of non-required options.
  # config.vm.synced_folder "../data", "/vagrant_data"

  # Provider-specific configuration so you can fine-tune various
  # backing providers for Vagrant. These expose provider-specific options.
  # Example for VirtualBox:
  #
  # config.vm.provider "virtualbox" do |vb|
  #   # Display the VirtualBox GUI when booting the machine
  #   vb.gui = true
  #
  #   # Customize the amount of memory on the VM:
  #   vb.memory = "1024"
  # end
  #
  # View the documentation for the provider you are using for more
  # information on available options.

  # Define a Vagrant Push strategy for pushing to Atlas. Other push strategies
  # such as FTP and Heroku are also available. See the documentation at
  # https://docs.vagrantup.com/v2/push/atlas.html for more information.
  # config.push.define "atlas" do |push|
  #   push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME"
  # end

  # Enable provisioning with a shell script. Additional provisioners such as
  # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
  # documentation for more information about their specific syntax and use.
  # config.vm.provision "shell", inline: <<-SHELL
  #   sudo apt-get update
  #   sudo apt-get install -y apache2
  # SHELL
end

めっちゃ英語 (((;゚Д゚))) ですが、変えるのはここだけ。

# Create a private network, which allows host-only access to the machine
# using a specific IP.
config.vm.network :private_network, ip: "192.168.33.10"

プライベートネットワークを有効、ゲストOSのIPアドレスを "192.168.33.10" とし、vagrant reload で再起動します。

$ vagrant reload
==> default: Attempting graceful shutdown of VM...
==> default: Clearing any previously set forwarded ports...
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
    default: Adapter 2: hostonly
==> default: Forwarding ports...
    default: 22 => 2222 (adapter 1)
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2222
    default: SSH username: vagrant
    default: SSH auth method: private key
    default: Warning: Connection timeout. Retrying...
    default: Warning: Connection timeout. Retrying...
    default: Warning: Remote connection disconnect. Retrying...
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
==> default: Configuring and enabling network interfaces...
==> default: Mounting shared folders...
    default: /vagrant => C:/Users/k_horii/Documents/Projects/delivery/vagrant
==> default: Machine already provisioned. Run `vagrant provision` or use the `--provision`
==> default: to force provisioning. Provisioners marked to run always will still run.

前回の起動時は Adapter 1: nat だけだったのが、Adapter 2: hostonly が追加されています。

ゲストOSに入ってネットワーク設定を確認してみます。

$ vagrant ssh
Welcome to your Vagrant-built virtual machine.
[vagrant@localhost ~]$ ifconfig
eth0      Link encap:Ethernet  HWaddr **:**:**:**:**:**
          inet addr:10.0.2.15  Bcast:10.0.2.255  Mask:255.255.255.0
          inet6 addr: fe80::a00:27ff:fe5a:fb02/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:774 errors:0 dropped:0 overruns:0 frame:0
          TX packets:579 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:81387 (79.4 KiB)  TX bytes:69757 (68.1 KiB)

eth1      Link encap:Ethernet  HWaddr **:**:**:**:**:**
          inet addr:192.168.33.10  Bcast:192.168.33.255  Mask:255.255.255.0
          inet6 addr: fe80::a00:27ff:fe87:e4a1/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:5 errors:0 dropped:0 overruns:0 frame:0
          TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:326 (326.0 b)  TX bytes:552 (552.0 b)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)

eth1 として 192.168.33.10 が追加されています。

ホストOSからゲストOS (192.168.33.10)にpingしてみます。

$ ping -n 3 192.168.33.10

192.168.33.10 に ping を送信しています 32 バイトのデータ:
192.168.33.10 からの応答: バイト数 =32 時間 <1ms TTL=64
192.168.33.10 からの応答: バイト数 =32 時間 <1ms TTL=64
192.168.33.10 からの応答: バイト数 =32 時間 <1ms TTL=64

192.168.33.10 の ping 統計:
    パケット数: 送信 = 3、受信 = 3、損失 = 0 (0% の損失)、
ラウンド トリップの概算時間 (ミリ秒):
    最小 = 0ms、最大 = 0ms、平均 = 0ms

ちゃんと届いています。

ゲストOS (192.168.33.10)からホストOSが所属するLAN内にある開発サーバ (192.168.1.100)にpingしてみます。

[vagrant@localhost ~]$ ping -c 3 192.168.1.100
PING 192.168.1.100 (192.168.1.100) 56(84) bytes of data.
64 bytes from 192.168.1.100: icmp_seq=1 ttl=63 time=4.11 ms
64 bytes from 192.168.1.100: icmp_seq=2 ttl=63 time=1.03 ms
64 bytes from 192.168.1.100: icmp_seq=3 ttl=63 time=0.969 ms

--- 192.168.1.100 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 0.969/2.039/4.111/1.465 ms

こちらも届きました。

念のため、LAN内にある開発サーバ (192.168.1.100)からゲストOS (192.168.33.10)にpingしてみます。

[develop@centos6 ~]$ ping -c 3 192.168.33.10
PING 192.168.33.10 (192.168.33.10) 56(84) bytes of data.

--- 192.168.33.10 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 12000ms

プライベートネットワークなので届きません。

参考にした記事

上の記事では更に、ゲストOS同士でのホストOSでのポートフォワーディングによって、ゲストOSに接続させる方法についても書かれています。

他にもQiitaの vagarnt タグを見ると、現在1200件以上の投稿があります。

共有フォルダのマウントで引っ掛かったり、CentOSだとカーネルを更新したらVirtualBox GuestAdditionsが壊れたとか、そういった情報が散見されます。

いくつか気になる記事をメモしておきます。

後はAnsibleを覚えて、コマンドコピペ作業からちゃんと卒業したいです。

(Fabricは…今のところバックアップ作業や開発サーバへのgitリポジトリ作成などに使ってますが、再利用可能なコマンドとしてまとめる程の作業量が発生しないというか…)

Windowsでgitコミット差分抽出+ZIPアーカイブを生成するバッチファイル

git diff コマンドと git archive コマンドを利用した差分抽出については知ってはいたんですが、しばらくFTPでのアップロードが必要なプロジェクトから逃れていたこともあり、あまり追求していませんでした。

今回再びそのような環境に携わることになりましたので、Windowsで実現する方法を調べて試してみたところ、以下のようなバッチファイルでうまくいきました。

git-archive-diff.bat

@echo off
setlocal ENABLEDELAYEDEXPANSION
set DIFF_LIST=
set NEW_SHA=%1
set OLD_SHA=%2
if "%OLD_SHA%" == "" (
  set OLD_SHA=%NEW_SHA%
  set ARCHIVE="%NEW_SHA:~0,7%.zip"
) else (
  set ARCHIVE="%OLD_SHA:~0,7%-%NEW_SHA:~0,7%.zip"
)
for /f "usebackq" %%A in (`git diff --name-only --diff-filter=AM %OLD_SHA%..%NEW_SHA%`) do set DIFF_LIST=!DIFF_LIST! %%A
git archive --format=zip HEAD -o %ARCHIVE% %DIFF_LIST%
endlocal

SourceTreeから実行

自分はこのバッチファイルをSourceTreeから利用していますので、参考までに設定の手順を記しておきます。

適当な場所に配置した上で、 [Tools] → [Options] → [Custom Actions] → [Add] でカスタムアクション名とバッチファイルのパスを指定し、パラメータに $SHA と入力します。

例ではカスタムアクション名を「archive-diff」としています。オプションのチェックボックスはチェックしなくていいです。

差分を抽出したいコミットを2つ選択し、右クリックから [Custom Actions] → [archive-diff] と選択すると、登録したバッチファイルが実行され、リポジトリのルートに差分を抽出したZIPファイルが作成されます。

コマンドラインから実行

$ git-archive-diff {新しい方のコミットID} {古い方のコミットID}

引数で指定するコミットIDの順序が直感的ではありませんが、SourceTreeのカスタムアクションの仕様に合わせてこうなっています。

コマンドラインで利用する場合はバッチファイルの set NEW_SHA=%1set OLD_SHA=%2 のところを %1 ←→ %2 として、引数を入れ替えた方が分かりやすいかもしれません。

参考

スクリプトの内容は、こちらの質問および回答を参考にさせていただきました。(参考というかほとんどそのままです…)

git diffコマンドにdiff-filterオプションを追加したのと、出力ファイル名をコミットIDから生成する処理を追加しただけです。

回答にもありますが、コマンドプロンプトや変数で扱える文字数に制限がある(8191文字?)とのことで、対象ファイルが多い、またファイル名が長いとエラーが発生するかもしれません。

なお、以下のMicrosoft技術情報はWindows XPのものですが、長すぎるパラメータはファイルから読み込むようにすればこの制限を回避することができるようです。

以下の記事はバッチファイルの仕様を把握するのにお世話になりました。

PHPは言語仕様がクソ」とかよく言われますが、PHPの文法は分かりやすいよなぁ、というのが真性PHPerな自分の正直な感想です…。

(記号ばっかり…同じ記号の意味が文脈で変わる…エスケープどうすればいいの…なんでコマンドラインとバッチファイルで違うの…みたいな)

Bowerを使ってみたメモ

まず node.js が必要らしい。

最新バージョンのインストーラ http://nodejs.org/dist/v0.10.33/node-v0.10.33-x86.msi を取得して、全部デフォルトのままインストール。パス環境変数への追加なども勝手にやってくれるみたい。

以下、シェルは NYAOS にて。

$ node --version
v0.10.33

きたきた。

Bower は node.js のパッケージ管理ツール npm でインストールするそうな。

グローバルにインストール。グローバルって何処?よく分からんけどとりあえずユーザーのホームディレクトリから実行してみた。

$ npm install -g bower
C:\Users\k_horii\AppData\Roaming\npm\bower -> C:\Users\k_horii\AppData\Roaming\npm\node_modules\bower\bin\bower
bower@1.3.12 C:\Users\k_horii\AppData\Roaming\npm\node_modules\bower
├── is-root@1.0.0
├── junk@1.0.0
├── stringify-object@1.0.0
├── chmodr@0.1.0
├── abbrev@1.0.5
├── which@1.0.5
├── osenv@0.1.0
├── opn@1.0.0
├── archy@0.0.2
├── bower-endpoint-parser@0.2.2
├── bower-logger@0.2.2
├── graceful-fs@3.0.4
├── lru-cache@2.5.0
├── rimraf@2.2.8
├── lockfile@1.0.0
├── retry@0.6.0
├── nopt@3.0.1
├── tmp@0.0.23
├── q@1.0.1
├── semver@2.3.2
├── p-throttler@0.1.0 (q@0.9.7)
├── request-progress@0.3.0 (throttleit@0.0.2)
├── shell-quote@1.4.2 (array-filter@0.0.1, array-reduce@0.0.0, array-map@0.0.0, jsonify@0.0.0)
├── bower-json@0.4.0 (intersect@0.0.3, graceful-fs@2.0.3, deep-extend@0.2.11)
├── promptly@0.2.0 (read@1.0.5)
├── mkdirp@0.5.0 (minimist@0.0.8)
├── chalk@0.5.0 (ansi-styles@1.1.0, escape-string-regexp@1.0.2, supports-color@0.2.0, has-ansi@0.1.0, strip-ansi@0.3.0)
├── bower-config@0.5.2 (osenv@0.0.3, graceful-fs@2.0.3, optimist@0.6.1)
├── cardinal@0.4.0 (redeyed@0.4.4)
├── bower-registry-client@0.2.1 (lru-cache@2.3.1, async@0.2.10, graceful-fs@2.0.3, request-replay@0.2.0, mkdirp@0.3.5,request@2.27.0)
├── handlebars@2.0.0 (optimist@0.3.7, uglify-js@2.3.6)
├── mout@0.9.1
├── decompress-zip@0.0.8 (mkpath@0.1.0, nopt@2.2.1, touch@0.0.2, binary@0.3.0, readable-stream@1.1.13)
├── request@2.42.0 (json-stringify-safe@5.0.0, caseless@0.6.0, forever-agent@0.5.2, aws-sign2@0.5.0, stringstream@0.0.4, oauth-sign@0.4.0, tunnel-agent@0.4.0, mime-types@1.0.2, node-uuid@1.4.1, qs@1.2.2, tough-cookie@0.12.1, form-data@0.1.4, http-signature@0.10.0, hawk@1.1.1, bl@0.9.3)
├── tar-fs@0.5.2 (pump@0.3.5, tar-stream@0.4.7)
├── glob@4.0.6 (inherits@2.0.1, once@1.3.1, minimatch@1.0.0)
├── fstream-ignore@1.0.1 (minimatch@1.0.0, inherits@2.0.1)
├── fstream@1.0.2 (inherits@2.0.1)
├── insight@0.4.3 (object-assign@1.0.0, async@0.9.0, tough-cookie@0.12.1, os-name@1.0.1, chalk@0.5.1, lodash.debounce@2.4.1, configstore@0.3.1, inquirer@0.6.0)
├── update-notifier@0.2.0 (semver-diff@0.1.0, string-length@0.1.2, configstore@0.3.1, latest-version@0.2.0)
└── inquirer@0.7.1 (figures@1.3.3, mute-stream@0.0.4, through@2.3.6, lodash@2.4.1, readline2@0.1.0, rx@2.3.14, cli-color@0.3.2)

なんだこれ…やたら一杯入ってびびるわ…。

$ bower -v
1.3.12

なんか反応が遅いけど、とりあえずプロジェクトのホームに移動して使ってみる。

$ bower init
[?] May bower anonymously report usage statistics to improve the tool over time?

そのまま

? name: (chrony)

現在のディレクトリ名が勝手に入るっぽい。そのまま

? version: (0.0.0)

プロジェクトのバージョン?そのまま

? description:

そのまま

? main file:

そのまま

? what types of modules does this package expose? (Press <space> to select)
( ) amd
( ) es6
( ) globals
( ) node
( ) yui

何言ってるか分からんがそのまま

? keywords:

そのまま

? authors: (k-holy <k.holy74@gmail.com>)

gitの設定から勝手に取ってくるっぽい?そのまま

license: (MIT)

そのまま

? homepage:

そのまま

? set currently installed components as dependencies?: (Y/n)

はい

? add commonly ignored files to ignore list?: (Y/n)

はい

? would you like to mark this package as private which prevents it from being accidentally published to the registry? (y/N)

はい

{
  name: 'chrony',
  version: '0.0.0',
  authors: [
    'k-holy <k.holy74@gmail.com>'
  ],
  license: 'MIT',
  private: true,
  ignore: [
    '**/.*',
    'node_modules',
    'bower_components',
    'test',
    'tests'
  ]
}
? Looks good? (Y/n)

はい

カレントディレクトリに bower.json が作成された。

jQueryを入れてみる。

$ bower install jquery --save

bower_components 以下に jquery が入った。

bower.jsonは以下の通り。

{
  "name": "chrony",
  "version": "0.0.0",
  "authors": [
    "k-holy <k.holy74@gmail.com>"
  ],
  "license": "MIT",
  "private": true,
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ],
  "dependencies": {
    "jquery": "~2.1.1"
  }
}

で、これどうやってドキュメントルート以下から参照するの…?

.bowerrc 設定ファイルに定義すればインストール先を変更できるらしいので、変えてみる。

一旦 bower_components は削除。

以下の内容で .bowerrc ファイルを作成して…

{
    "directory": "public/bower_components"
}

再インストール。すでに bower.json に依存は定義されているのでこれでOKかな?

$ bower install
bower jquery#~2.1.1             cached git://github.com/jquery/jquery.git#2.1.1
bower jquery#~2.1.1           validate 2.1.1 against git://github.com/jquery/jquery.git#~2.1.1
bower jquery#~2.1.1            install jquery#2.1.1

jquery#2.1.1 public\bower_components\jquery

OK。次は Bootstrap を入れる。

$ bower install bootstrap --save
bower bootstrap#*           not-cached git://github.com/twbs/bootstrap.git#*
bower bootstrap#*              resolve git://github.com/twbs/bootstrap.git#*
bower bootstrap#*             download https://github.com/twbs/bootstrap/archive/v3.3.0.tar.gz
bower bootstrap#*              extract archive.tar.gz
bower bootstrap#*             resolved git://github.com/twbs/bootstrap.git#3.3.0
bower bootstrap#~3.3.0         install bootstrap#3.3.0

bootstrap#3.3.0 public\bower_components\bootstrap
└── jquery#2.1.1

テンプレートからの呼び出しはこんな感じに修正する。

<script src="/bower_components/jquery/dist/jquery.min.js"></script>
<script src="/bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="/bower_components/bootstrap/dist/css/bootstrap.css" />

JavaScriptフレームワークとか全然分からん、クソみたいな自作jQueryプラグインで何とかやってる、そんなレベルの自分でも大丈夫でした。

後は、サーバ側にも同じように node.js を入れて bower コマンドを入れて、デプロイ時には bower install すると楽できるというわけですか。

node.js なんてまるで興味ない&特に必要もされていないLAMPおじさんな自分が、そのためだけに node.js を入れるというのは微妙な気がするけども…。

参考にした記事

Windows環境のPHPで日本語ファイルパスを扱う場合の注意点

まず手元の Windows7 + PHP 5.6.0 で検証した上での結論を書くと Windows で日本語ファイルパスを扱う場合 SplFileInfo は使うな」 です。

ロケール設定を適切に行うことで basename()pathinfo() に関しては、Shift_JISのコード表に起因するいわゆる「5C問題」も回避できました。

しかし、SplFileInfo::getBasename()SplFileInfo::isFile() に関しては、適切にロケールを設定したとしても、残念ながら「5C問題」を回避できなかったのです。

ロケール設定の影響を受ける関数について

文字列関数は基本的にマルチバイト非対応のものが多いのですが、中には対応はしているがロケール設定に依存する、というものもあります。というか結構多いです。

ファイルパスを扱う際によく使われる basename()dirname()pathinfo() などは典型的で、Web上ではよくこれらの関数を「日本語に対応していない」と書かれた記事を見かけますが、実際にはそうではなく、ロケール設定が不適切なケースがほとんどだと思います。

他にも見落としがちなところでは、バリデーション処理にも使われる ctype_alpha() などのctype関数や、strtoupper()strcasecmp() のような大文字小文字を変換したり比較する関数でも、ロケール設定によっては予想外の結果を返す場合があります。

これらロケール設定の影響を受ける関数のまとめは、こちらの記事が参考になります。

トラブルを避けるためには、アプリケーションにおける内部エンコーディングロケール設定を揃えるのはもちろんですが、一時的に異なったエンコーディングを扱う場合は setlocale() 関数を併用することも必要になります。

たとえば fgetcsv()SplFileObjectCSVファイルを扱う場合、「Excelで開ける」という要件を満たそうとすると、一般的なLinux系OSのロケール設定のままでは対応できません。

また、自分のようにLinux環境ではあえてCロケールPOSIXロケール?)に設定している場合でも、これらの関数でマルチバイト文字列を扱う際に setlocale() は必須になります。

PHPUnitでの検証結果

今回は検証のために、PHPUnitでテストコードを書きました。

その結果から分かったことを挙げていきます。

  • pathinfo()['dirname']dirname() は同じ結果を返す
  • pathinfo()['basename']basename() は同じ結果を返す

上記は予想通りでした。

  • pathinfo()['dirname']SplFileInfo::getPath() は必ずしも同じ結果を返さない
  • pathinfo()['basename']SplFileInfo::getBasename() は必ずしも同じ結果を返さない

この辺はちょっと予想外。内部的には同じコードを使っているのかと思ってましたが、どうやら違うようで…これが今回の結論に至った要因です。

おかしな結果が返されたのは以下のケースです。

  • 日本語(非5C)で終わるディレクトリをパスに含むファイルへの dirname() および SplFileInfo::getPath()
  • 日本語(5C)で終わるファイルへの SplFileInfo::getBasename()
  • 日本語(5C)で終わるファイルへの SplFileInfo::isFile()
  • 日本語(5C)で終わるディレクトリへの SplFileInfo::getPathname()
  • 日本語(5C)で終わるディレクトリへの SplFileInfo::getBasename()
  • 日本語(5C)で終わるディレクトリへの SplFileInfo::isDir()
  • 日本語(5C)で終わるディレクトリをパスに含むファイルへの SplFileInfo::getType()
  • 日本語(5C)で終わるディレクトリをパスに含むファイルへの SplFileInfo::isFile()

これらの結果から判断して、 SplFileInfo はロケールを適切に設定しても日本語ファイルパスを正しく扱えない と考えて良いと思います。

一方で、basename()pathinfo()ロケールを適切に設定することで日本語ファイルパスを正しく扱える と考えて良いと思います。

念のため書いておくと、UTF-8であれば5C問題とか無関係なので当然ですが、LinuxUTF-8の日本語ファイルパスについては何ら問題ありません。

Windowsでも実際のファイルパスではなく単なる文字列としてのファイルパス(SplFileInfoは存在しないファイルパスも扱えます)を内部エンコーディングUTF-8で扱う場合も、問題はありません。

また、存在しないファイルやディレクトリへの SplFileInfo::isFile()SplFileInfo::isDir() そして SplFileInfo::getRealPath() が常にFALSEを返すのは予想通りだと思いますが、SplFileInfo::getType() など実際のファイル情報が必要なメソッドが RuntimeException をスローするのは要注意です。(PHPマニュアルにもある通りですが)

Windows環境でSplFileInfoがダメとなれば、それを継承している場合もおそらく同様、ということはSymfonyのFinderコンポーネントも…?

PHPUnitでの検証ソース

長くなりますが、PHPUnitでの検証ソースです。

テストを通すためにアサーションを修正したものなので、おかしいのはテスト結果ではなく、行末コメントで「!!」と書いてあるアサーションの方です。

メソッド名がでたらめ英語なのはご容赦ください…。)

<?php
namespace Acme\Test;

class SplFileInfoTest extends \PHPUnit_Framework_TestCase
{

    private $isWin;
    private $testDir;

    public function setUp()
    {
        $this->isWin = (strncasecmp(PHP_OS, 'WIN', 3) === 0);
        $this->testDir = __DIR__ . DIRECTORY_SEPARATOR . 'SplFileInfoTest';
    }

    private function convert($path)
    {
        return mb_convert_encoding($path, 'CP932', 'UTF-8');
    }

    // 日本語(非5C)のファイル(Windows)
    public function testJapaneseFileOnWindows()
    {
        if (!$this->isWin) {
            $this->markTestSkipped('OS is not Windows');
        }

        $this->setLocale(LC_CTYPE, 'Japanese_Japan.932');
        $path = $this->convert($this->testDir . DIRECTORY_SEPARATOR . '日本語.txt');
        $fileInfo = new \SplFileInfo($path);
        $pathinfo = pathinfo($path);

        $this->assertEquals($path, $fileInfo->getRealPath());
        $this->assertEquals($path, $fileInfo->getPathname());
        $this->assertEquals($this->convert('日本語.txt'), $pathinfo['basename']);
        $this->assertEquals($pathinfo['basename'], $fileInfo->getBasename());
        $this->assertEquals($pathinfo['basename'], basename($path));
        $this->assertEquals($this->testDir, $pathinfo['dirname']);
        $this->assertEquals($pathinfo['dirname'], $fileInfo->getPath());
        $this->assertEquals($pathinfo['dirname'], dirname($path));
        $this->assertEquals( 'txt', $pathinfo['extension']);
        $this->assertEquals($pathinfo['extension'], $fileInfo->getExtension());
        $this->assertEquals('file', $fileInfo->getType());
        $this->assertTrue($fileInfo->isFile());
        $this->assertFalse($fileInfo->isDir());
    }

    // 日本語(非5C)のディレクトリ(Windows)
    public function testJapaneseDirectoryOnWindows()
    {
        if (!$this->isWin) {
            $this->markTestSkipped('OS is not Windows');
        }

        $this->setLocale(LC_CTYPE, 'Japanese_Japan.932');
        $path = $this->convert($this->testDir . DIRECTORY_SEPARATOR . '日本語');
        $fileInfo = new \SplFileInfo($path);
        $pathinfo = pathinfo($path);

        $this->assertEquals($path, $fileInfo->getRealPath());
        $this->assertEquals($path, $fileInfo->getPathname());
        $this->assertEquals($this->convert('日本語'), $pathinfo['basename']);
        $this->assertEquals($pathinfo['basename'], $fileInfo->getBasename());
        $this->assertEquals($pathinfo['basename'], basename($path));
        $this->assertEquals($this->testDir, $pathinfo['dirname']);
        $this->assertEquals($pathinfo['dirname'], $fileInfo->getPath());
        $this->assertEquals($pathinfo['dirname'], dirname($path));
        $this->assertArrayNotHasKey('extension', $pathinfo);
        $this->assertEmpty($fileInfo->getExtension());
        $this->assertEquals('dir', $fileInfo->getType());
        $this->assertFalse($fileInfo->isFile());
        $this->assertTrue($fileInfo->isDir());
    }

    // 日本語(非5C)のディレクトリをパスに含むファイル(Windows)
    public function testFileIncludesJapaneseInPathOnWindows()
    {
        if (!$this->isWin) {
            $this->markTestSkipped('OS is not Windows');
        }

        $this->setLocale(LC_CTYPE, 'Japanese_Japan.932');
        $path = $this->convert($this->testDir . DIRECTORY_SEPARATOR . '日本語' . DIRECTORY_SEPARATOR . 'test.txt');
        $fileInfo = new \SplFileInfo($path);
        $pathinfo = pathinfo($path);

        $this->assertEquals($path, $fileInfo->getRealPath());
        $this->assertEquals($path, $fileInfo->getPathname());
        $this->assertEquals('test.txt', $pathinfo['basename']);
        $this->assertEquals($pathinfo['basename'], $fileInfo->getBasename());
        $this->assertEquals($pathinfo['basename'], basename($path));
        $this->assertNotEquals($this->convert($this->testDir . DIRECTORY_SEPARATOR . '日本語'), $pathinfo['dirname']); // !!
        $this->assertNotEquals($pathinfo['dirname'], $fileInfo->getPath()); // !!
        $this->assertEquals($pathinfo['dirname'], dirname($path));
        $this->assertEquals('txt', $pathinfo['extension']);
        $this->assertEquals($pathinfo['extension'], $fileInfo->getExtension());
        $this->assertEquals('file', $fileInfo->getType());
        $this->assertTrue($fileInfo->isFile());
        $this->assertFalse($fileInfo->isDir());
    }

    // 日本語(5C)のファイル(Windows)
    public function testJapaneseFileIncludes5cOnWindows()
    {
        if (!$this->isWin) {
            $this->markTestSkipped('OS is not Windows');
        }

        $this->setLocale(LC_CTYPE, 'Japanese_Japan.932');
        $path = $this->convert($this->testDir . DIRECTORY_SEPARATOR . '表.txt');
        $fileInfo = new \SplFileInfo($path);
        $pathinfo = pathinfo($path);

        $this->assertFalse($fileInfo->getRealPath()); // !!
        $this->assertEquals($path, $fileInfo->getPathname());
        $this->assertEquals($this->convert('表.txt'), $pathinfo['basename']);
        $this->assertNotEquals($pathinfo['basename'], $fileInfo->getBasename()); // !!
        $this->assertEquals($pathinfo['basename'], basename($path));
        $this->assertEquals($this->testDir, $pathinfo['dirname']);
        $this->assertNotEquals($pathinfo['dirname'], $fileInfo->getPath()); // !!
        $this->assertEquals($pathinfo['dirname'], dirname($path));
        $this->assertEquals( 'txt', $pathinfo['extension']);
        $this->assertEquals($pathinfo['extension'], $fileInfo->getExtension());
        $this->assertEquals('file', $fileInfo->getType());
        $this->assertFalse($fileInfo->isFile()); // !!
        $this->assertFalse($fileInfo->isDir());
    }

    // 日本語(5C)のディレクトリ(Windows)
    public function testJapaneseDirectoryIncludes5cOnWindows()
    {
        if (!$this->isWin) {
            $this->markTestSkipped('OS is not Windows');
        }

        $this->setLocale(LC_CTYPE, 'Japanese_Japan.932');
        $path = $this->convert($this->testDir . DIRECTORY_SEPARATOR . '');
        $fileInfo = new \SplFileInfo($path);
        $pathinfo = pathinfo($path);

        $this->assertFalse($fileInfo->getRealPath()); // !!
        $this->assertNotEquals($path, $fileInfo->getPathname()); // !!
        $this->assertEquals($this->convert(''), $pathinfo['basename']);
        $this->assertNotEquals($pathinfo['basename'], $fileInfo->getBasename()); // !!
        $this->assertEquals($pathinfo['basename'], basename($path));
        $this->assertEquals($this->testDir, $pathinfo['dirname']);
        $this->assertEquals($pathinfo['dirname'], $fileInfo->getPath());
        $this->assertEquals($pathinfo['dirname'], dirname($path));
        $this->assertArrayNotHasKey('extension', $pathinfo);
        $this->assertEmpty($fileInfo->getExtension());
        $this->assertEquals('dir', $fileInfo->getType());
        $this->assertFalse($fileInfo->isFile());
        $this->assertFalse($fileInfo->isDir()); // !!
    }

    // 日本語(5C)のディレクトリをパスに含むファイル(Windows)
    public function testFileIncludesJapaneseIncludes5cInPathOnWindows()
    {
        if (!$this->isWin) {
            $this->markTestSkipped('OS is not Windows');
        }

        $this->setLocale(LC_CTYPE, 'Japanese_Japan.932');
        $path = $this->convert($this->testDir . DIRECTORY_SEPARATOR . '' . DIRECTORY_SEPARATOR . 'test.txt');
        $fileInfo = new \SplFileInfo($path);
        $pathinfo = pathinfo($path);

        $this->assertFalse($fileInfo->getRealPath()); // !!
        $this->assertEquals($path, $fileInfo->getPathname());
        $this->assertEquals('test.txt', $pathinfo['basename']);
        $this->assertEquals($pathinfo['basename'], $fileInfo->getBasename());
        $this->assertEquals($pathinfo['basename'], basename($path));
        $this->assertEquals($this->convert($this->testDir . DIRECTORY_SEPARATOR . ''), $pathinfo['dirname']);
        $this->assertEquals($pathinfo['dirname'], $fileInfo->getPath());
        $this->assertEquals($pathinfo['dirname'], dirname($path));
        $this->assertEquals('txt', $pathinfo['extension']);
        $this->assertEquals($pathinfo['extension'], $fileInfo->getExtension());
        try {
            $this->assertEquals('file', $fileInfo->getType());
        } catch (\RuntimeException $e) {
            $this->assertStringStartsWith('SplFileInfo::getType(): Lstat failed', $e->getMessage()); // !!
        }
        $this->assertFalse($fileInfo->isFile()); // !!
        $this->assertFalse($fileInfo->isDir());
    }

    // 日本語のファイル(非Windows)
    public function testJapaneseFile()
    {
        if ($this->isWin) {
            $this->markTestSkipped('OS is Windows');
        }

        $this->setLocale(LC_CTYPE, 'ja_JP.UTF-8');
        $path = $this->testDir . DIRECTORY_SEPARATOR . '日本語.txt';
        $fileInfo = new \SplFileInfo($path);
        $pathinfo = pathinfo($path);

        $this->assertEquals($path, $fileInfo->getRealPath());
        $this->assertEquals($path, $fileInfo->getPathname());
        $this->assertEquals('日本語.txt', $pathinfo['basename']);
        $this->assertEquals($pathinfo['basename'], $fileInfo->getBasename());
        $this->assertEquals($pathinfo['basename'], basename($path));
        $this->assertEquals($this->testDir, $pathinfo['dirname']);
        $this->assertEquals($pathinfo['dirname'], $fileInfo->getPath());
        $this->assertEquals($pathinfo['dirname'], dirname($path));
        $this->assertEquals( 'txt', $pathinfo['extension']);
        $this->assertEquals($pathinfo['extension'], $fileInfo->getExtension());
        $this->assertEquals('file', $fileInfo->getType());
        $this->assertTrue($fileInfo->isFile());
        $this->assertFalse($fileInfo->isDir());
    }

    // 日本語(非5C)のディレクトリ(非Windows)
    public function testJapaneseDirectory()
    {
        if ($this->isWin) {
            $this->markTestSkipped('OS is Windows');
        }

        $this->setLocale(LC_CTYPE, 'ja_JP.UTF-8');
        $path = $this->testDir . DIRECTORY_SEPARATOR . '日本語';
        $fileInfo = new \SplFileInfo($path);
        $pathinfo = pathinfo($path);

        $this->assertEquals($path, $fileInfo->getRealPath());
        $this->assertEquals($path, $fileInfo->getPathname());
        $this->assertEquals('日本語', $pathinfo['basename']);
        $this->assertEquals($pathinfo['basename'], $fileInfo->getBasename());
        $this->assertEquals($pathinfo['basename'], basename($path));
        $this->assertEquals($this->testDir, $pathinfo['dirname']);
        $this->assertEquals($pathinfo['dirname'], $fileInfo->getPath());
        $this->assertEquals($pathinfo['dirname'], dirname($path));
        $this->assertArrayNotHasKey('extension', $pathinfo);
        $this->assertEmpty($fileInfo->getExtension());
        $this->assertEquals('dir', $fileInfo->getType());
        $this->assertFalse($fileInfo->isFile());
        $this->assertTrue($fileInfo->isDir());
    }

    // 日本語(非5C)のディレクトリをパスに含むファイル(非Windows)
    public function testFileIncludesJapaneseInPath()
    {
        if ($this->isWin) {
            $this->markTestSkipped('OS is Windows');
        }

        $this->setLocale(LC_CTYPE, 'ja_JP.UTF-8');
        $path = $this->testDir . DIRECTORY_SEPARATOR . '日本語' . DIRECTORY_SEPARATOR . 'test.txt';
        $fileInfo = new \SplFileInfo($path);
        $pathinfo = pathinfo($path);

        $this->assertEquals($path, $fileInfo->getRealPath());
        $this->assertEquals($path, $fileInfo->getPathname());
        $this->assertEquals('test.txt', $pathinfo['basename']);
        $this->assertEquals($pathinfo['basename'], $fileInfo->getBasename());
        $this->assertEquals($pathinfo['basename'], basename($path));
        $this->assertEquals($this->testDir . DIRECTORY_SEPARATOR . '日本語', $pathinfo['dirname']);
        $this->assertEquals($pathinfo['dirname'], $fileInfo->getPath());
        $this->assertEquals($pathinfo['dirname'], dirname($path));
        $this->assertEquals('txt', $pathinfo['extension']);
        $this->assertEquals($pathinfo['extension'], $fileInfo->getExtension());
        $this->assertEquals('file', $fileInfo->getType());
        $this->assertTrue($fileInfo->isFile());
        $this->assertFalse($fileInfo->isDir());
    }

    // 存在しない日本語のファイル
    public function testJapaneseFileNotExisting()
    {
        if (!$this->isWin) {
            $this->setLocale(LC_CTYPE, 'ja_JP.UTF-8');
        }

        $path = $this->testDir . DIRECTORY_SEPARATOR . 'ソ.txt';
        $fileInfo = new \SplFileInfo($path);
        $pathinfo = pathinfo($path);

        $this->assertFalse($fileInfo->getRealPath());
        $this->assertEquals($path, $fileInfo->getPathname());
        $this->assertEquals('ソ.txt', $pathinfo['basename']);
        $this->assertEquals($pathinfo['basename'], $fileInfo->getBasename());
        $this->assertEquals($pathinfo['basename'], basename($path));
        $this->assertEquals($this->testDir, $pathinfo['dirname']);
        $this->assertEquals($pathinfo['dirname'], $fileInfo->getPath());
        $this->assertEquals($pathinfo['dirname'], dirname($path));
        $this->assertEquals( 'txt', $pathinfo['extension']);
        $this->assertEquals($pathinfo['extension'], $fileInfo->getExtension());
        try {
            $fileInfo->getType();
        } catch (\RuntimeException $e) {
            $this->assertStringStartsWith('SplFileInfo::getType(): Lstat failed', $e->getMessage());
        }
        $this->assertFalse($fileInfo->isFile());
        $this->assertFalse($fileInfo->isDir());
    }

    // 存在しない日本語のディレクトリ
    public function testJapaneseDirectoryNotExisting()
    {
        if (!$this->isWin) {
            $this->setLocale(LC_CTYPE, 'ja_JP.UTF-8');
        }

        $path = $this->testDir . DIRECTORY_SEPARATOR . '';
        $fileInfo = new \SplFileInfo($path);
        $pathinfo = pathinfo($path);

        $this->assertFalse($fileInfo->getRealPath());
        $this->assertEquals($path, $fileInfo->getPathname());
        $this->assertEquals('', $pathinfo['basename']);
        $this->assertEquals($pathinfo['basename'], $fileInfo->getBasename());
        $this->assertEquals($pathinfo['basename'], basename($path));
        $this->assertEquals($this->testDir, $pathinfo['dirname']);
        $this->assertEquals($pathinfo['dirname'], $fileInfo->getPath());
        $this->assertEquals($pathinfo['dirname'], dirname($path));
        $this->assertArrayNotHasKey('extension', $pathinfo);
        $this->assertEmpty($fileInfo->getExtension());
        try {
            $fileInfo->getType();
        } catch (\RuntimeException $e) {
            $this->assertStringStartsWith('SplFileInfo::getType(): Lstat failed', $e->getMessage());
        }
        $this->assertFalse($fileInfo->isFile());
        $this->assertFalse($fileInfo->isDir());
    }

    // 存在しない日本語のディレクトリをパスに含むファイル
    public function testFileIncludesJapaneseInPathNotExisting()
    {
        if (!$this->isWin) {
            $this->setLocale(LC_CTYPE, 'ja_JP.UTF-8');
        }

        $path = $this->testDir . DIRECTORY_SEPARATOR . '' . DIRECTORY_SEPARATOR . 'test.txt';
        $fileInfo = new \SplFileInfo($path);
        $pathinfo = pathinfo($path);

        $this->assertFalse($fileInfo->getRealPath());
        $this->assertEquals($path, $fileInfo->getPathname());
        $this->assertEquals('test.txt', $pathinfo['basename']);
        $this->assertEquals($pathinfo['basename'], $fileInfo->getBasename());
        $this->assertEquals($pathinfo['basename'], basename($path));
        $this->assertEquals($this->testDir . DIRECTORY_SEPARATOR . '', $pathinfo['dirname']);
        $this->assertEquals($pathinfo['dirname'], $fileInfo->getPath());
        $this->assertEquals($pathinfo['dirname'], dirname($path));
        $this->assertEquals('txt', $pathinfo['extension']);
        $this->assertEquals($pathinfo['extension'], $fileInfo->getExtension());
        try {
            $fileInfo->getType();
        } catch (\RuntimeException $e) {
            $this->assertStringStartsWith('SplFileInfo::getType(): Lstat failed', $e->getMessage());
        }
        $this->assertFalse($fileInfo->isFile());
        $this->assertFalse($fileInfo->isDir());
    }

}

過去記事の補足

上記の過去記事では「Linux版PHP5.3.14でbasename(), pathinfo()が期待する結果を返さない」と書いてますが、今となっては当時の設定を覚えてませんが、多分単なるロケール設定の誤りだと思います。

ローカル開発環境(Windows7)のPHPを5.5.16から5.6.0に更新した

ローカル開発環境(32bit Windows7)でのPHP 5.6への更新作業メモです。

アーカイブを展開してシンボリックリンクを切り替える

最新リリース版をダウンロード

過去バージョンの動作環境もそのまま残しておくため、シンボリックリンクで切り替えます。

mklink /d LINK TARGET コマンドで、アーカイブを展開したディレクトリにシンボリックリンクを切り替えます。Linuxのlnコマンドとは逆順です。(要管理者権限)

以下、シェルはNYAOSを使っています。

$ rmdir c:\php
$ mklink /d c:\php c:\php-5.6.0-Win32-VC11-x86

環境変数 Path には c:\php を設定しているので…。

$ php -v
PHP 5.6.0 (cli) (built: Aug 27 2014 11:54:39)
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2014 Zend Technologies

OKです。

旧バージョンで追加/編集したファイルをコピーする

旧バージョンから追加/編集したファイルをコピーします。

  • composer.bat
  • composer.phar
  • php.ini

システム環境変数で PATH に c:\php を追加してPHP関連のコマンドを置いてる都合上、composerコマンドもコピーします。

ちなみにPEARを使っていた頃はここがPEARbin_dir でもあったため、PEARでインストールした Stagehand_TestRunner の testrunner コマンドと testrunner.bat もここにありました。

今はこちらの記事の通り、脱PEAR済みです。

新旧バージョンのphp.ini-developmentで変更箇所をチェックする

php.iniはとりあえず旧バージョンのものをコピーして編集しますが、まずは新旧のphp.ini-developmentを比較して変更箇所を差分ツール等で確認します。

今回の 5.5.16 → 5.6.0 に関しては微妙な英文の変更を除くと以下のような感じ。

マルチバイト関連

5.6.0では、default_charsetがUTF-8に指定されたのと、新たに internal_encoding, input_encoding, output_encoding ディレクティブが追加されています。

5.5.16

; PHP's default character set is set to empty.
; http://php.net/default-charset
;default_charset = "UTF-8"

5.6.0

; PHP's default character set is set to UTF-8
; http://php.net/default-charset
default_charset = "UTF-8"

; PHP internal character encoding is set to empty.
; If empty, default_charset is used.
; http://php.net/internal-encoding
;internal_encoding =

; PHP input character encoding is set to empty.
; If empty, default_charset is used.
; http://php.net/input-encoding
;input_encoding =

; PHP output character encoding is set to empty.
; If empty, default_charset is used.
; mbstring or iconv output handler is used.
; See also output_buffer.
; http://php.net/output-encoding
;output_encoding =

mbstringへの影響については後述。

$HTTP_RAW_POST_DATA

PHPマニュアルによれば -1 にすると将来のバージョンでの挙動に合わせられる、つまり $HTTP_RAW_POST_DATA が未定義となるようです。

5.5.16

; Always populate the $HTTP_RAW_POST_DATA variable. PHP's default behavior is
; to disable this feature. If post reading is disabled through
; enable_post_data_reading, $HTTP_RAW_POST_DATA is *NOT* populated.
; http://php.net/always-populate-raw-post-data
;always_populate_raw_post_data = On

5.6.0

; Always populate the $HTTP_RAW_POST_DATA variable. PHP's default behavior is
; to disable this feature and it will be removed in a future version.
; If post reading is disabled through enable_post_data_reading,
; $HTTP_RAW_POST_DATA is *NOT* populated.
; http://php.net/always-populate-raw-post-data
;always_populate_raw_post_data = -1

要するにデフォルト設定では未定義になったということかな。まあ、使っていないので関係ありません。

iconv

先ほど見た intput_encoding, internal_encoding, output_encoding ディレクティブの新設に合わせて、適用順序の説明が追加されています。

5.5.16

[iconv]
;iconv.input_encoding = ISO-8859-1
;iconv.internal_encoding = ISO-8859-1
;iconv.output_encoding = ISO-8859-1

5.6.0

[iconv]
; Use of this INI entry is deprecated, use global input_encoding instead.
; If empty, default_charset or input_encoding or iconv.input_encoding is used.
; The precedence is: default_charset < intput_encoding < iconv.input_encoding
;iconv.input_encoding =

; Use of this INI entry is deprecated, use global internal_encoding instead.
; If empty, default_charset or internal_encoding or iconv.internal_encoding is used.
; The precedence is: default_charset < internal_encoding < iconv.internal_encoding
;iconv.internal_encoding =

; Use of this INI entry is deprecated, use global output_encoding instead.
; If empty, default_charset or output_encoding or iconv.output_encoding is used.
; The precedence is: default_charset < output_encoding < iconv.output_encoding
; To use an output encoding conversion, iconv's output handler must be set
; otherwise output encoding conversion cannot be performed.
;iconv.output_encoding =

iconvも使ってないので関係なし。

mbstring

こちらもiconvと同様、intput_encoding, internal_encoding, output_encoding ディレクティブの新設に合わせて、適用順序の説明などが追加されています。

5.5.16

; internal/script encoding.
; Some encoding cannot work as internal encoding.
; (e.g. SJIS, BIG5, ISO-2022-*)
; http://php.net/mbstring.internal-encoding
;mbstring.internal_encoding = UTF-8

; http input encoding.
; http://php.net/mbstring.http-input
;mbstring.http_input = UTF-8

; http output encoding. mb_output_handler must be
; registered as output buffer to function
; http://php.net/mbstring.http-output
;mbstring.http_output = pass

5.6.0

; Use of this INI entry is deprecated, use global internal_encoding instead.
; internal/script encoding.
; Some encoding cannot work as internal encoding. (e.g. SJIS, BIG5, ISO-2022-*)
; If empty, default_charset or internal_encoding or iconv.internal_encoding is used.
; The precedence is: default_charset < internal_encoding < iconv.internal_encoding
;mbstring.internal_encoding =

; Use of this INI entry is deprecated, use global input_encoding instead.
; http input encoding.
; mbstring.encoding_traslation = On is needed to use this setting.
; If empty, default_charset or input_encoding or mbstring.input is used.
; The precedence is: default_charset < intput_encoding < mbsting.http_input
; http://php.net/mbstring.http-input
;mbstring.http_input =

; Use of this INI entry is deprecated, use global output_encoding instead.
; http output encoding.
; mb_output_handler must be registered as output buffer to function.
; If empty, default_charset or output_encoding or mbstring.http_output is used.
; The precedence is: default_charset < output_encoding < mbstring.http_output
; To use an output encoding conversion, mbstring's output handler must be set
; otherwise output encoding conversion cannot be performed.
; http://php.net/mbstring.http-output
;mbstring.http_output =

この辺の設定は .htaccess とかアプリケーションのコードで変更しているので、あまり関係ないのですが、Use of this INI entry is deprecated とあります。

基本は default_charset のみ指定して、後はコメントアウトしておけば問題ないかと。つまり、5.6.0のデフォルト設定に従えばよし。

openssl

5.5.16にはなかった openssl モジュールのディレクティブが追加されています。

5.6.0

[openssl]
; The location of a Certificate Authority (CA) file on the local filesystem
; to use when verifying the identity of SSL/TLS peers. Most users should
; not specify a value for this directive as PHP will attempt to use the
; OS-managed cert stores in its absence. If specified, this value may still
; be overridden on a per-stream basis via the "cafile" SSL stream context
; option.
;openssl.cafile=

; If openssl.cafile is not specified or if the CA file is not found, the
; directory pointed to by openssl.capath is searched for a suitable
; certificate. This value must be a correctly hashed certificate directory.
; Most users should not specify a value for this directive as PHP will
; attempt to use the OS-managed cert stores in its absence. If specified,
; this value may still be overridden on a per-stream basis via the "capath"
; SSL stream context option.
;openssl.capath=

ここで指定しておくと、SSLコンテキストオプション のデフォルト値として使われるみたいです。

php.iniを編集する

新旧バージョンでチェックしたphp.ini-developmentの変更内容を取り込みます。

元々ポータビリティ重視でphp.iniは極力変更せずアプリケーション毎に .htaccess や ini_set() を使う方針なので、今回変更したのはここだけでした。

5.5.16

mbstring.http_output = pass

5.6.0

;mbstring.http_output =

その他には php_oci8.dll, php_oci8_11g.dll がなくなって php_oci8_12c.dll に変わってたりもしますが、使ってないので関係なし…。

自分で追加したextensionのDLLファイルを入手する

今回はマイナーバージョン更新のため、自分で追加したextensionのDLLファイルも入れ替えになると思います。(といっても今ではXdebugくらいしか使ってませんが…)

Xdebugは公式サイトでWindows用DLLファイルが提供されているので、これをダウンロードします。

インストールしたPHPのバージョンに合わせて選択します。今回はこちら。

c:\php\ext に保存して、php.iniの該当箇所を変更。

[XDebug]
zend_extension_ts = "ext/php_xdebug-2.2.5-5.6-vc11.dll"