今までJavaをメインに、PythonやRubyも使って仕事したり記事を書いてきたりしましたが、来月から「PHPでWeb開発!」なお仕事をいただけましたので、絶賛(あわてて)PHP入門中です。
ただ一人で勉強していても退屈なので、この記事では僕がPHPを勉強する上でどんなことをしてどんなことを疑問に思ったのかを記録として書いてみたいと思います。
新しいプログラミング言語を勉強するときの視点の一つとして参考になれば幸いです。
環境
- Ubuntu 14.04.5
- PHP 5.5.9-1ubuntu4.23 (cli)
- VIM version 7.4.52
前提
この記事は、PHP マニュアルの目次をざーっと読んで、「はじめに」あたりをなんとなく読んだあたりの状態からスタートします。
「自分もPHPはこれから勉強します!」という方は先にこのあたりを10分くらい斜め読みしてみると話が早いかと思います。
また、PHPと言ったらWebサーバー上で動かしてHTMLを構築する用途で利用することが多いかと思いますが、今回は言語自体の勉強のためCLIで、PHPのソースファイルをコマンドライン上で実行する、という方法で進めています。
本文
それではさっそく、カンタンなプログラムを書きながらPHPの勉強を進めていきます。
プログラミングの勉強でまず問題になるのが「何のプログラムを書こう?」ということなのですが、よくありがちなカンタンな足し算引き算やFooBar、AnimalとCatみたいなプログラムでは僕のモチベーションが続かなそうな気がしましたので、今回は短くてもちゃんと動くプログラムを最初に用意してみたいと思います。
<?php
$fp = fsockopen("www.hasam.jp", 80, $errno, $errstr, 30);
if (!$fp) {
echo "$errstr ($errno)\n";
} else {
$out = "GET / HTTP/1.1\r\n";
$out .= "Host: www.hasam.jp\r\n";
$out .= "Connection: Close\r\n";
$out .= "\r\n";
fwrite($fp, $out);
while (!feof($fp)) {
echo fgets($fp, 128);
}
fclose($fp);
}
これは、TCPのソケット通信のAPIだけを使ってwww.hasam.jp
へHTTPのGETリクエストを送り、返却データを出力する、という処理です。なんでこんなプログラムにしたかと言うと、単純にマイブームだからです。
なお、↑のプログラムは僕もちゃんと理解して書いたワケではなく、以下のページのほぼコピペです。
今回はソケット通信の勉強がメインではないため、一旦ここはコピペでOKということで。
保存したら実行してみます。
$ php tiny_http_client.php
# 出力は長いので割愛
無事、http://www.hasam.jpのトップページのHTMLが文字列で出力されました。ちゃんと動くと気持ちが良いですね。
あ、Hasamというのは僕が個人でやってるサービスです。「キレイな廃墟」ということで一部界隈では有名です。
関数にしてみる
さて、このままでは実行時に上から下へとただ流れるだけのプログラムで、使い捨てスクリプト感が漂いすぎているので、少しずつ改善しながらPHPに慣れていこうと思います。
まずは処理全体を関数にし、アクセス先のホストくらいは引数で受け取れるようにしてみます。以下のドキュメントを参考にします。
function
で囲って、引数は変数宣言と同様、型とか無しで$
始まりの変数名をつけて、、、とやって、以下のようになりました。
<?php
request("www.hasam.jp");
function request($host) {
$fp = fsockopen($host, 80, $errno, $errstr, 30);
if (!$fp) {
echo "$errstr ($errno)\n";
} else {
$out = "GET / HTTP/1.1\r\n";
$out .= "Host: $host\r\n";
$out .= "Connection: Close\r\n";
$out .= "\r\n";
fwrite($fp, $out);
while (!feof($fp)) {
echo fgets($fp, 128);
}
fclose($fp);
}
}
これで、request()
関数の引数を変えれば別のサイトからもデータを取ってこられますね。ついでにホストはソースにベタ書きではなく実行時に引数で渡すように修正することで、コマンドライン引数の参照方法やif文の書き方なんかも練習しておきます。
<?php
if (count($argv) > 1) {
$host = $argv[1];
} else {
$host = "www.hasam.jp";
}
request($host);
function request($host) {
// 以下、修正無しのため省略
Javaと違って、if
のブロックの中で宣言した変数がブロックの外で参照できるということも、ついでに分かりました。ただしこのあたりの仕様は言語によってさまざまで、バグにもつながりやすいポイントですので、詳しくはTODOとして後で調べておこうと思います。
また、コマンドライン引数のドキュメントを見てみると、冒頭に
注意: この変数は、register_argc_argv が無効になっている場合には使えません。
と書かれています。どうやらPHPは実行時にいろいろと設定を変更できる仕組みがあるみたいです。軽くリンク先を見てみると、どうやらphp.ini
ファイルにいろいろ書くことでこのあたりの設定を変更できるみたいです。
コア php.ini ディレクティブに関する説明 | PHP
このあたりもTODOで要調査ですね。
ここまで、処理全体を関数にしてみることで、関数の書き方やコマンドライン引数の参照方法、if
文の書き方(ちょっとだけ)など、いろいろなことが分かりました。
と同時に、php.ini
の使い方や設定内容、ブロックと変数の関係など、今後調べておくべき内容も出てきました。
こんな感じで「とりあえず試してみて、出てきた疑問を溜めていって、その疑問をひとつずつ調査して」という感じで進めていく作戦です。
もう少し続きます。
クラスにしてみる
言語リファレンスを眺めていると、PHPにもクラスがあります。文法としてクラスがあるということは、きっとJavaやRubyなど他の言語と同様にそれなりの規模の開発現場ではクラスが使われているはずです。
というワケで、次は先ほどの処理をクラスで分割してみたいと思います。以下のページを参考にします。
まずはカンタンに、リクエストの内容を保持するRequest
クラスを作ってみます。と言っても今のところ変更できるのはホストくらいですので、プロパティは$host
のみです。
class
の宣言自体はJavaと似ていて、public
修飾子があって、自身のインスタンスは$this
で表して、$this
の$host
プロパティを参照する場合は->
記号で、その場合host
の方の$
はなくなって、、、としているうちに、以下のようになりました。
function request($host) {
$request = new Request();
$request->setHost($host);
$fp = fsockopen($request->host, 80, $errno, $errstr, 30);
if (!$fp) {
echo "$errstr ($errno)\n";
} else {
$out = "GET / HTTP/1.1\r\n";
$out .= "Host: $request->host\r\n";
$out .= "Connection: Close\r\n";
// このあたりは修正無しのため省略
}
}
class Request {
public $host = "";
public function setHost($host) {
$this->host = $host;
}
}
うーん、ひとまずプロパティとメソッドをひとつずつ定義してみましたが、よく考えたらPHPってプロパティのアクセスにgetter/setterメソッドって使うんですかね?ついJavaのクセで書こうとしてしまいましたが、このあたりも要調査です。
ひとまず↑のように書いてみましたが、これくらいならJavaでいうコンストラクタを使ってnew
と同時に$host
もセットしてしまいたいところです。と思ってたら先ほどのクラスのページからリンクがありました。
読んでみると、クラスに__construct()
関数を用意するのが基本のキのようです。先ほどのコードをコンストラクタを使って修正します。
// 省略
function request($host) {
$request = new Request($host);
$fp = fsockopen($request->host, 80, $errno, $errstr, 30);
// 省略
}
}
class Request {
public $host = "";
public function __construct($host) {
$this->host = $host;
}
}
スッキリしましたね。これでなんとなくクラス分けもできそうです。
ファイル分割
せっかくクラスを切り出したので、同じファイルに書くのではなくRequest
クラスは別ファイルにちゃんと分けておきたいと思います。
今後Response
みたいなデータクラスが増えることも想定して、data
ディレクトリにrequest.php
ファイルを作り、そこに先ほどのRequest
クラスを書いておきましょう。
<?php
class Request {
public $host = "";
public function __construct($host) {
$this->host = $host;
}
}
この状態でtiny_http_client.php
を実行すると、当然ですが「Request
クラスなんてないよ」エラーで失敗します。
$ php tiny_http_client.php www.hasam.jp
PHP Fatal error: Class 'Request' not found in /home/chooyan/workspace/php/tiny_http_client.php on line 12
ドキュメントを探すと、別ファイルのインポートは大きく分けてrequire
とimport
の2種類があるようです。
require
のページによると、
require は include とほぼ同じですが、失敗した場合に E_COMPILE_ERROR レベルの致命的なエラーも発生するという点が異なります。 つまり、スクリプトの処理がそこで止まってしまうということです。一方 include の場合は、警告 (E_WARNING) を発するもののスクリプトの処理は続行します。
ということで、失敗した場合のプログラムの扱いが異なるようです。それ以外のことは書かれていないため、上記以外はどちらも変わらないということかと思います。ドキュメントの説明を読む限りですが。
今回の場合、失敗したら後続の処理は続けられないため、require
を使うことにしてみます。tiny_http_client.php
に1行追加します。
<?php
require("data/request.php");
if (count($argv) > 1) {
// 以下略
再度実行するとちゃんと動きました。良さそうですね。
ただこの場合、今後プログラムの規模が大きくなって参照するファイルが多くなればなるほどrequire
が増えていきます。その対処として、依存関係を管理するcomposer
というツールのautoload
という仕組みが使えるそうです。
Documentation#Autoloading | COMPOSER
のですが、サンプルソースを読んでみたりしていると、autoload
を使う場合はクラスのアクセスの際にnamespace\data\Request
のように、バックスラッシュ区切りでネームスペースを含めて記述しなければならないようで、Javaに慣れた身としてはとてもカルチャーショックを受ける感じでした。
ひとまず今はcomposer
というものがあるんだー、くらいにしておきたいと思います。
ひとまずここまで
ということで、この記事ではPHPの入門として、関数、メソッド、ファイル分割あたりを中心に、コマンドライン引数やphp.ini
についても軽く触れてみました。
と同時に、今後の要調査項目として
- 変数のスコープ
-
php.ini
の使い方 - setter/getterについて
あたりが勉強する中で出てきました。
本文には出てきませんが、他にも
- 変数、関数、クラス、メソッドの命名規則(キャメルケース?スネークケース?)
- インデントはスペース何個?
みたいなコーディング規約的なことだったり、
- PHPのバージョンによってどれだけ挙動や仕様違うのか?
- CLIじゃなくてWebアプリ開発の場合は何がどう違うのか?
- 「PHPっぽい」書き方って何だろう?
などなど、これから仕事で使う上で必要そうなこともいろいろ疑問が湧いてきました。
今後はこのあたりを順番に理解を深めていこうと思います。
この記事で参照したPHPドキュメントもまだまだ全然目を通せていませんので、こちらも引き続き読み進めていきます。
他にも何か良い記事や書籍がありましたら、是非コメントいただければ幸いです。