PHPを愛する試み 〜調教編〜
PHPを愛する試みというのを個人的にやっている。
最近仕事でPHPをガリガリ使わなければならない状況になってしまった。そのため可及的速やかにPHPを愛する必要がある。
どうやるか
ざっと調べたところ、
・どんなオブジェクトでもtapとpを使いたい。
・けどPHPにはRubyのように基底クラスを簡単に書き換えるような仕組みはなさそう
・かといって全てのクラスにメソッド実装するのはダルイ
・C言語で拡張とか僕には無理だお
という感じだった。しかし、さらに調べるとrubyのactivesupportっぽいことをPHPでやってる洋モノの変態さんを発見した。
ActiveSupport for PHP - Ruby style
なんかよさそう。ということで、ここから発想を得て以下のような仕組みを考えてみた。
・任意のオブジェクト$objをruby()に食わせると、->tap()と->p()が使えるようになる
・->tap()と->p()はメソッドチェインで->tap()->p()のように繋げることができる
・ruby()に食わせたオブジェクトは->php()で元のオブジェクトに戻せる
Rubyっぽいメソッドを叩き込むドSファイル
<?php class Ruby { public function __construct($origin) { $this->origin = $origin; } public function tap($callback) { $callback($this->origin); return $this; } public function p() { var_dump($this->origin); return $this; } public function php() { return $this->origin; } } function ruby($origin) { return new Ruby($origin); }
使ってみる
rubynize.php
<?php require 'ruby.php'; class C { public function __construct() { $this->name = 'hoge'; } } // オブジェクトをvar_dump $c = new C(); ruby($c)->p(); // オブジェクトのnameプロパティにfugeをセット $c = new C(); ruby($c)->tap(function ($x) {$x->name = 'fuge';}); var_dump($c); // オブジェクトのnameプロパティにfugeをセットし、オブジェクトをvar_dump $c = new C(); ruby($c)->tap(function ($x) {$x->name = 'fuge';})->p(); // 上記の返り値をRubynizeする前のものにする $c = new C(); $new_c = ruby($c)->tap(function ($x) {$x->name = 'fuge';})->p()->php(); var_dump($new_c);
チェイン(鎖)ー!
さらに使ってみる
rubynize2.php
<?php require 'ruby.php'; class C { public function setName($name) { $this->name = $name; return $this; } public function setSex($sex) { $this->sex = $sex; return $this; } } // 普通のメソッドチェイン $c = new C(); $c->setName('hoge')->setSex('man'); var_dump($c); // tapでnameをhoge->fugeに $c = new C(); ruby($c->setName('hoge')) ->tap(function ($x) {$x->name = 'fuge';}) ->php() ->setSex('man'); var_dump($c); // tapでnameをhoge->fugeに。sexをman->womanに。さらにチェーンでvar_dump $c = new C(); ruby( ruby( ruby($c->setName('hoge')) ->tap(function ($x) {$x->name = 'fuge';}) ->php() ->setSex('man') ) ->tap(function ($x) {$x->sex = 'woman';}) )->p();
チェインチェイン(鎖鎖)ー!
終わり
ruby()が必要だったり->php()しなきゃだったりで、もっとすっきり出来そう。
だけど自分の能力ではこの辺が限界だお...
もっとPHPのことを知らなきゃですな...
※ 個人的にPHPとたわむれる試みなので、実用性とか(ry
※シリーズもので頑張ってる
PHPを愛する試み
PHPを愛する試み 〜調教編〜
PHPを愛する試み 〜self:: parent:: static:: および遅延静的束縛〜
unshift, shift, pop, pushが混乱するので、絵で整理した
配列の追加・取り出しに関する4つのメソッド(unshift, shift, pop, push)がいつも混乱するので絵で整理した。
絵にすれば覚えられそう!いちお練習のため、自分がよく使うRuby, PHP, JavaScriptでコードも書いた。
・いずれの言語においても上記4つのメソッドは似たような名前で存在しており、用途も同じ。
・いずれの言語のいずれのメソッドも破壊的メソッド。
・言語、バージョンによってメソッドの返り値は異なる。
という具合だった。
Ruby
ruby 1.9.2
# unshift a = [1, 2, 3] b = a.unshift 0 p a #[0, 1, 2, 3] p b #[0, 1, 2, 3] # shift a = [1, 2, 3] b = a.shift p a #[2, 3] p b #1 # pop a = [1, 2, 3] b = a.pop p a #[1, 2] p b #3 # push a = [1, 2, 3] b = a.push 4 p a #[1, 2, 3, 4] p b #[1, 2, 3, 4] # << a = [1, 2, 3] b = a << 4 p a #[1, 2, 3, 4] p b #[1, 2, 3, 4]
PHP
PHP 5.3.10
<?php // unshift $a = array(1, 2, 3); $b = array_unshift($a, 0); var_dump($a); //array(0, 1, 2, 3) var_dump($b); //4(結果配列の要素数) // shift $a = array(1, 2, 3); $b = array_shift($a); var_dump($a); //array(2, 3) var_dump($b); //1 // pop $a = array(1, 2, 3); $b = array_pop($a); var_dump($a); // array(1, 2) var_dump($b); // 3 // push $a = array(1, 2, 3); $b = array_push($a, 4); var_dump($a); //array(1, 2, 3, 4) var_dump($b); //4 (結果配列の要素数) // [] = $a = array(1, 2, 3); $b = $a[] = 4; var_dump($a); //array(1, 2, 3, 4) var_dump($b); //4(追加した値)
JavaScript
node 0.6.12
var a = [1, 2, 3]; var b = a.unshift(0); console.log(a); //[0, 1, 2, 3] console.log(b); //4(結果配列の要素数) var a = [1, 2, 3]; var b = a.shift(); console.log(a); //[2, 3] console.log(b); //1 var a = [1, 2, 3]; var b = a.pop(); console.log(a); //[1, 2] console.log(b); //3 var a = [1, 2, 3]; var b = a.push(4); console.log(a); //[1, 2, 3, 4] console.log(b); //4(結果配列の要素数)
一瞬で「今日のディレクトリ」を作成・移動できるRuby/Bashスクリプト
ちょっとしたファイルを作成・保存しておきたい時、自分にとっては「今日の日付のディレクトリ」にまとめておくのが丁度よい。これまでは毎日手作業で「今日の日付のディレクトリ」を作成・移動していたのだけど、これを毎回やるのが面倒臭い。ということでスクリプトで自動化した。ちなみに環境はruby1.9.2。bash 3.2.48。
※追記
ブコメでご指摘いただいた通り、Ruby使わないでBashだけでできます...なぜわざわざRubyを使ったのか...そこにRubyがあったからです...
機能
・シェルで「. today」と入力すると「~/daily/今日の日付(yyyy_mm_dd)」ディレクトリを作成し移動する
実装にあたって
Rubyでディレクトリを作るのは簡単なのだが、現在のシェルを移動するのに一工夫必要だった。RubyでDir.chdirするだけでは、現在のシェルのディレクトリは移動しない(あくまでもrubyコマンドを実行したシェルの子プロセスとして動くから)。そこで「Rubyはディレクトリ作成後、移動コマンド(cd ...)を標準出力に吐き出す→吐き出されたコマンドを現在のシェルで実行する」という処理にした。
標準出力に吐き出された文字列をBashのコマンドとして実行するには「コマンド置換($()か``)」を使う
コマンド置換
コマンド置換 (command substitution) を用いると、 コマンド名をコマンドの出力で置き換えられます。...
http://linuxjm.sourceforge.jp/html/GNU_bash/man1/bash.1.html
コマンドを現在のシェルで実行するには「.かsource」を使う
. filename [arguments]
source filename [arguments]filename からコマンドを読み込み、現在のシェル環境の下で実行します。...
http://linuxjm.sourceforge.jp/html/GNU_bash/man1/bash.1.html
コードを書く
Ruby
~/script/today.rb
today = Time.now.strftime "%Y_%m_%d" today_dir = File.expand_path("~/daily/#{today}") Dir.mkdir(today_dir) unless File.exist? today_dir puts "cd #{today_dir}"
実行
※追記
ご指摘いただいたので、lsの表示結果を掲載しました
例えば、今日の日付が2013年1月20日で、この状態から
$ pwd /Users/hidenorimaehara $ ls ~/daily 2013_01_18 2013_01_19
シェルで以下のコマンドを打つと
$ . today
※「.」は「source」でもOK
今日の日付のディレクトリが作成され移動できる。
$ pwd /Users/hidenorimaehara/daily/2013_01_20 $ ls ../ 2013_01_18 2013_01_19 2013_01_20
となる
Macなら、こうすれば、ディレクトリ移動後にそのディレクトリをFinderを開けて便利。
$ . today
$ open .
終わり
ちょっとしたスクリプトだけど、自分にとっては結構便利だった!
PHPを愛する試み
僕はRubyが好きだ。プライベートではRubyばっかり使っている。でも、仕事ではPHPを使わなければならない。これまでPHPは書きにくいーと思い込んでいてあまり好きではなかったのだけど、仕事で使う以上PHPを好きになった方がきっと幸せになれる。何かを好きになるにはどうすればよいか。そう、相手のことを知る努力をすればいいんだ!ということで、PHPについて知る努力をしてみた。
これは...
PHP好きになれるかも!
普段Rubyで書いてるコードをPHPで書いてみる
折角なので自分が普段Rubyを使っていて便利だなーと感じていることをPHPではどう書くのか(厳密では無いにしても大体同じ内容はどう書くか)試してみた。Rubyは1.9.2、PHPは5.3.10。
多重代入っぽいもの
a, b = [1, 2] puts a puts b
<?php list($a, $b) = array(1, 2); echo "$a\n"; echo "$b\n";
ほう!
可変長引数
def foo(*args) p args end foo 1, 2 foo 1, 2, 3
<?php function foo() { var_dump(func_get_args()); } foo(1, 2); foo(1, 2, 3);
ほほう!
無名関数・クロージャ
a = [1, 2, 3] b = 10 r = a.map {|v| v * b} p r
<?php $a = array(1, 2, 3); $b = 10; $r = array_map(function($v) use ($b) {return $v * $b;}, $a); var_dump($r);
名前空間
module A class C def self.foo; puts 'A_foo'; end end end module B class C def self.foo; puts 'B_foo'; end end end A::C.foo B::C.foo
<?php namespace A{ class C { static function foo() {echo "A_foo\n";} } } namespace B{ class C { static function foo() {echo "B_foo\n";} } } namespace { \A\C::foo(); \B\C::foo(); }
キモい!けど、きっと慣れる!
呼び出し元を一覧表示
def foo1; foo2; end def foo2; foo3; end def foo3; puts caller; end foo1
<?php function foo1() {foo2();} function foo2() {foo3();} function foo3() {debug_print_backtrace();} foo1();
これできるんだ!
オブジェクトの情報をいろいろ表示
class C; def foo1; end end class CC < C; def foo2; end end class CCC < CC; def foo3; end end ccc = CCC.new puts ccc.class.ancestors puts ccc.methods
<?php class C { function foo1() {} } class CC extends C { function foo2() {} } class CCC extends CC { function foo3() {} } $ccc = new CCC(); var_dump(get_class($ccc)); var_dump(class_parents($ccc)); var_dump(get_class_methods($ccc)); //さらにReflectionObject::exportを使うと、メソッドの定義場所やインスタンス変数まで表示してくれる ReflectionObject::export($ccc);
ソースコード読むとき助かる!
メソッドチェイン
class C def foo1; self; end def foo2; self; end def foo3; puts 'done'; end end C.new.foo1.foo2.foo3
<?php class C { function foo1() {return $this;} function foo2() {return $this;} function foo3() {echo 'done';} } $c = new C(); $c->foo1()->foo2()->foo3();
流れてる...
メタプログラミング(動的な関数定義と呼び出し)
class C def method_missing(method_name, *args) puts method_name end end c = C.new [:foo1, :foo2].each {|m| c.__send__(m)}
<?php class C { function __call($method_name, $args) { echo "$method_name\n"; } } $c = new C(); foreach(array("foo1", "foo2") as $m) {$c->$m();}
こいつ・・・動くぞ!
さらにPHP5.4にすると
スゴイらしい...
http://php.net/manual/ja/migration54.new-features.php
配列のリテラルとして[]が使えたり、ビルトインサーバが使えたりするらしいですお...
PHP
好きになれるかも!今度はフレームワークとかも触ってみよう!
※シリーズもので頑張ってる
PHPを愛する試み
PHPを愛する試み 〜調教編〜
PHPを愛する試み 〜self:: parent:: static:: および遅延静的束縛〜
Rubyで内部DSLっぽいのやってみる
少し前に仕事中、一瞬の隙も見逃さずに情報収集できるRubyワンライナーとスクリプトというRubyスクリプトを書いたのだが、スクレイピング対象サイトをハッシュで持ってるあたりが使いづらい。良い機会なので内部DSLってのに挑戦してみた。
しかし、内部DSLについてざっと調べたものの、まだ租借しきれていない。とりあえず、Sinatraっぽい構文でスクレイピング対象サイトを指定できるようにした。
sites.rb(ユーザー側)
fetch 'yahoo' do url 'http://www.yahoo.co.jp' regexp /topics.+?>([^<]+?)</ end fetch 'hatena' do url 'http://b.hatena.ne.jp/hotentry' regexp /entry-link.+>(.+?)</ end fetch 'naver' do url 'http://matome.naver.jp/' regexp /matomename.+?-->(.+?)<!--/m end fetch '2ch' do url 'http://uni.2ch.net/newsplus/' regexp /<a.+?>\d+?:\s(.+?)</ end
news(定義側)
#!/usr/bin/env ruby require 'open-uri' require 'nkf' class Site attr_accessor :name, :url, :regexp def initialize(n) @name = n end def url(u) @url = u end def regexp(r) @regexp = r end def get open(@url) do |f| f.read.scan(@regexp) {|m| puts NKF.nkf('-w', m.join)} end end end @defined_sites = [] def fetch(name, &block) s = Site.new(name) s.instance_eval &block @defined_sites << s end load File.expand_path('../sites.rb', __FILE__) defined_site_names = @defined_sites.map(&:name) site_names = ARGV.empty? ? defined_site_names : ARGV sites = [] site_names.each do |site_name| raise 'not defined' unless defined_site_names.include? site_name @defined_sites.map {|s| sites << s if s.name === site_name} end sites.map(&:get)
実行
上記2ファイルを同じディレクトリに配置し、newsに実行権限を与え、パスを通せば前回と同様に動く。
Rubyワンライナー入門
先日仕事中、一瞬の隙も見逃さずに情報収集できるRubyワンライナーとスクリプトというネタエントリを書いたのだが、その際Rubyのワンライナーをもう少しまともに理解したいと思ったので入門してみた。手元のRubyは1.9.2
はじめに
Rubyのワンライナーは、rubyコマンドに色々なオプション(-eや-nなど)をつけて実行する。各オプションの網羅的な解説が見たければ、コマンドラインでは$ man rubyで閲覧できるし、webではるりまサーチで参照できる。ただ、これらは網羅的すぎるので、以下よく使うオプションとその周辺情報についてまとめた。
-n
プログラム全体がwhile gets ... endというループで囲まれているように動作する。ちょっとややこしい。
例: hoge.txtとfuge.txtの内容を表示
$ ruby -ne 'puts $_' hoge.txt fuge.txt
理解するには
Kernel.#gets、ARGF、$_について理解する必要がある
Kernel.#gets
ARGFから一行読み込んで、それを返します。...読み込んだ文字列は組み込み変数 $_ にもセットされます。...
http://rurema.clear-code.com/1.9.2/method/Kernel/m/gets.html
つまり、以下は同義
$ ruby -ne 'puts $_' hoge.txt fuge.txt
$ ruby -e 'while gets; puts $_; end' hoge.txt fuge.txt
ざっくりいうと、以下のような流れになる
hoge.txtとfuge.txtを連結した仮想ファイル(ARGF)をセット
↓
ARGFから1行読み込み、$_にセット。読み込めた場合は$_を出力してループ
ちなみに
上記では出力の部分をputs $_としているが、これはKernel.#printで代替できる。
Kernel.#print
引数を順に標準出力 $stdout に出力します。引数が与えられない時には変数 $_ の値を出力します。...
http://rurema.clear-code.com/1.9.2/method/Kernel/m/print.html
つまり、以下は全て同義
$ ruby -ne 'print' hoge.txt fuge.txt
$ ruby -ne 'puts $_' hoge.txt fuge.txt
$ ruby -e 'while gets; puts $_; end' hoge.txt fuge.txt
-p
nオプションとほぼ同じ。nオプションと同じくプログラム全体をwhile gets...endループするが、さらに各ループの最後にprintする。つまり、printが省略できるということ。なので、以下は全て同義。
$ ruby -pe '' hoge.txt fuge.txt
$ ruby -ne 'print' hoge.txt fuge.txt
$ ruby -ne 'puts $_' hoge.txt fuge.txt
$ ruby -e 'while gets; puts $_; end' hoge.txt fuge.txt
$_を破壊的メソッドで変換して出力するような場合に便利
例: hoge.txtとfuge.txtの内容を全て大文字にして表示
$ ruby -pe '$_.upcase!' hoge.txt fuge.txt
-a
オートスプリット。nかpと一緒に使う。各ループの先頭で$F = $_.splitをを実行する。
例: カレントディレクトリの情報を配列に変換して表示
$ ls -laF | ruby -ane 'p $F'
-F
$;(入力フィールドセパレータ。splitのデフォルト区切り文字)を設定
例: 環境変数PATHを:で分割して改行表示
$ echo $PATH | ruby -F: -ane 'puts $F'
-r
ライブラリ読み込み。
例: yahooのhtmlを取得
$ ruby -r open-uri -e 'open("http://www.yahoo.co.jp") {|f| puts f.read}'
ちなみに、複数のライブラリを読み込みたい場合は-r を複数書く。
例: facebookのapiからマークザッカーバーグのJSONを取得しハッシュに変換
$ ruby -r open-uri -r json -e 'open("http://graph.facebook.com/zuck") {|f| p JSON.parse(f.read)}'
BEGIN, END
オプションでは無いのだが、普段あまり使わないRubyの構文BEGINとEND(begin...endとは全くの別物)がワンライナーだと結構使われるようだ。
例: 全行が整数のnumber.txtの合計値を表示
$ ruby -ne 'BEGIN{$sum = 0}; $sum += $_.to_i; END{puts $sum}' number.txt
BEGINとENDをつけないと、毎行読み込むごとに$sum = 0; と puts $sumが実行されてしまうので意図した結果にならない
BEGIN
初期化ルーチンを登録します。BEGINブロックで指定した文は当該ファイルのどの文が実行されるより前に実行されます。...
http://doc.ruby-lang.org/ja/1.9.2/doc/spec=2fcontrol.html#BEGIN
END
「後始末」ルーチンを登録します。END ブロックで指定した文はインタプリタが終了する時に実行されます。Ruby の終了時処理について詳しくは 終了処理を参照してください。...
http://doc.ruby-lang.org/ja/1.9.2/doc/spec=2fcontrol.html#END
仕事中、一瞬の隙も見逃さずに情報収集できるRubyワンライナーとスクリプト
情報収集はビジネスマンとしての基本である。しかし普段シェルで作業する者としては、毎回ブラウザを立ち上げる時間すら不毛である。よって、シェルから一瞬で情報収集できるRubyのワンライナーを書いた。
上からYahoo, はてな, Naverまとめ, 2ちゃん
$ ruby -r open-uri -e 'open("http://www.yahoo.co.jp").read.scan(/topics.+?>([^<]+?)</) {|m| puts m}'
$ ruby -r open-uri -e 'open("http://b.hatena.ne.jp/hotentry").read.scan(/entry-link.+>(.+?)</) {|m| puts m}'
$ ruby -r open-uri -e 'open("http://matome.naver.jp/").read.scan(%r{matomename.+?-->(.+?)<!-}m) {|m| puts m}'
$ ruby -r open-uri -e 'open("http://uni.2ch.net/newsplus/").read.scan(/<a.+?>\d+?:\s(.+?)</) {|m| puts m}' | nkf -w
あれ?
普通にブラウザ開いた方が早いw
なので
コマンド一発で情報収集できるスクリプトを書いた
news
#!/usr/bin/env ruby require 'open-uri' require 'nkf' defined_sites = { 'yahoo' => { 'url' => 'http://www.yahoo.co.jp', 'regexp' => /topics.+?>([^<]+?)</ }, 'hatena' => { 'url' => 'http://b.hatena.ne.jp/hotentry', 'regexp' => /entry-link.+>(.+?)</ }, 'naver' => { 'url' => 'http://matome.naver.jp/', 'regexp' => /matomename.+?-->(.+?)<!--/m }, '2ch' => { 'url' => 'http://uni.2ch.net/newsplus/', 'regexp' => /<a.+?>\d+?:\s(.+?)</ } } class Site attr_accessor :url, :regexp def initialize yield self end def fetch open(@url) do |f| f.read.scan(@regexp) {|m| puts NKF.nkf('-w', m.join)} end end end site_names = ARGV.empty? ? defined_sites.keys : ARGV sites = [] site_names.each do |site_name| raise 'not defined' unless defined_sites.keys.include? site_name sites << Site.new do |s| s.url = defined_sites[site_name]['url'] s.regexp = defined_sites[site_name]['regexp'] end end sites.map(&:fetch)
こいつに実行権限を与えて、pathを通して、newsコマンド
$ news 東電社長 時効主張せずと明言 鳩山元首相、招かれ訪中へ 高2自殺 前日に平手30-40発? JTB 企業の体力測定事業参入 魔女狩りで禁固20年 ネパール 本田にミランがオファー準備 安藤美姫がトヨタ自動車退社 金爆・鬼龍院のNSC芸人時代 今日の話題(27件) 一番福へ全速力 ...
キタ!
yahooとhatenaだけ表示したければ
$ news yahoo hatena
対象サイトを追加したければ、ハッシュ(defined_sites )に追加すればOK!
これで一瞬の隙も見逃さずに情報収集する「できるビジネスマン」になれる!
※注 ネタです
※追記 Rubyワンライナーについてまとめた
Rubyワンライナー入門