2013/01/11(金)PHPはじめました

2013/01/11 22:24
来週からPHPの案件に参入することになりました。しかし、私のPHPのレベルと言えば、このブログのWordPressのテンプレートを少しいじったことはありますが、それ以上の知識はなく、到底業務で使えるものではないと思っています。本当に俺で良かったんだろうか。 とはいえ、「知らんからできない」では済まないので、ちょっと真面目に勉強してみることにしました。ネットの評判を見てみたところ、『パーフェクトPHP』という本が良さそうなので、これを教科書にして勉強しています。『パーフェクトJava』にはずいぶんとお世話になったので、きっとこれもいい本に違いない。
[amazonjs asin="4774144371" locale="JP"]
最終的にはPHPを使いこなせるようになりたいとは思いますが、当面の目標としては、自信満々に「JAVAできます」と言ってしまう自称Java技術者が書くJavaよりはマシなレベルのPHPを書けるようになることです。 ……ハードル下げすぎ? さて、この本は初版が2010年ということもあるのでしょうが、MacにPHPを導入する方法がMacPortsになっており、私の利用しているHomebrewでの設定方法は書かれていません。また、そもそも私はあまり環境を汚したくないので、できれば[MAMP](http://www.mamp.info/)を使ってコーディングしたいところでもあります。 というわけで、MAMPの場合の環境構築手順をまとめてみました。 #### インストール(p.24) [MAMP](http://www.mamp.info/)をダウンロードし、パッケージからインストールします。 #### ドキュメントルートの設定(p.24) 本の説明ではドキュメントルートを`/var/www/study.localhost`にしていますが、今回はMAMPの下の`/Applications/MAMP/htdocs/study.localhost`をドキュメントルートにしました。 sudo mkdir -p /Applications/MAMP/htdocs/study.localhost sudo chown ユーザ名 /Applications/MAMP/htdocs/study.localhost また、Apatchのバーチャルホストを設定するためにhttpd.confを編集しますが、ファイルのパスは`/Applications/MAMP/conf/apache/httpd.conf`になります。また、有効にする行の内容は下記になります。 Include /Applications/MAMP/conf/apache/extra/httpd-vhosts.conf 変更するhttpd-vhosts.confは`/Applications/MAMP/conf/apache/extra/httpd-vhosts.conf`になります。追加する内容は下記のとおり。 <VirtualHost *:80> ServerName study.localhost DocumentRoot /Applications/MAMP/htdocs/study.localhost DirectoryIndex index.php index.html AllowOverride All Allow from all 最後に、Apatchの動くポートを変える必要があります。MAMPの環境設定画面でポートタブを選択し、Apatchのポートに80番を設定します。 20130111 01 #### hostsにドメインを設定(p.25) ここは本のとおり。/etc/hostsに以下の行を追加します。 127.0.0.1 study.localhost これで学習の環境が整いました。

2013/01/10(木)「嫌い」と「悪い」は違う

2013/01/10 18:36
タイトルだけだと中田浩二の記事に見えますが、野球の話です。 [浩二の掟茶髪禁止「中田一喝せなあかん」 - プロ野球ニュース : nikkansports.com](http://www.nikkansports.com/baseball/news/p-bb-tp0-20130110-1070050.html) お年を召した指導者の方々はどうも茶髪やヒゲがお嫌いなようで、今回の山本監督の発言も別に驚くほどのことではありません。もちろん、まったく同意できる話ではなく、こちらとしては苦笑するしかないのですが。 > それはプロ野球選手である前に社会人として大切。 > [浩二の掟茶髪禁止「中田一喝せなあかん」 - プロ野球ニュース : nikkansports.com](http://www.nikkansports.com/baseball/news/p-bb-tp0-20130110-1070050.html) 茶髪を嫌う方々は二言目には「社会人として」と言いますが、そもそもプロ野球界という特殊な業界にいる人間に社会のことがわかるのでしょうか。私の知っている社会では、最近では直接顧客と接する立場であってもヒゲや茶髪は珍しくありません。もちろん、私の知っている社会が日本で一般的な社会であるとは断言できませんが、少なくともプロ野球界よりは一般的なところではあると思っています。 あと、ヒゲってわりと死活問題で、毎日剃ると肌がボロボロになる人もいるんですよ。まぁ、俺のことなんですけど。 実際のところはどうだかわかりませんが、建前としてはWBC出場は「ファンに夢を与える」という目的もあることになっています。では、ファンが何を求めているのか、もう一度考え直してもらいたい。我々は黒髪で礼儀正しく戦う代表を見たいのではなく、持ち味を発揮して勝ち進む代表を見たいのです。もちろん、手段を選ばずに勝ちに行けとまでは言いませんが、試合に臨む格好なんてどうでもいいと思っているのは私だけではないと思っています。 還暦を過ぎた方々が若かった頃は髪を染める人も少なかったでしょうし、それに不快感を示すのもわからないでもありません。また、その感情自体は別に間違っていることでもないと思います。禁止したいのであれば禁止すればよろしい。ただ、それならば素直に「俺が嫌だからやめろ」と言っていただきたい。自分の感情を一般論とすり替えるのはとても卑怯なことです。 ところで、こんなある意味ではどうでもいいような記事に異様に腹が立つのはなぜかなーと考えてみたら、お年を召した方のズレた意見に振り回される構図がうちの業界そっくりなんだ……。

2013/01/09(水)中村、ウォーキングのリハビリ開始

2013/01/09 23:07
左膝を手術した中村がウォーキングを開始しました。 [【西武】左膝手術おかわり6分間歩行訓練 - プロ野球ニュース : nikkansports.com](http://www.nikkansports.com/baseball/news/f-bb-tp0-20130109-1069918.html) 文字数制限のある見出しだからといって、「左膝手術おかわり」だと手術を失敗して再手術したみたいじゃねーか、というツッコミはおいておくとして。 もうウォーキングができるようになったということは、経過は順調だと考えて良さそうですね。まずはホッとしました。 ただ、順調だからといって急いで復帰を目指したりしないで、一歩一歩着実に先に進んで欲しいと思います。無理をして何日か、あるいは何週間か早く復帰したところで、その後の選手生命を縮めてしまうようでは意味がありません。私は回復の具合によっては最悪今年は復帰できなくても仕方がないくらいには思っていますので、くれぐれも焦らずに復帰を目指してもらいたいと思います。

2013/01/08(火)Djangoからtextsearch_jaを利用する

2013/01/08 20:32
Djangoからtextsearch_jaを呼び出す場合、素直にやるとORマッパーの恩恵を受けることができず、SQLのWHERE句をゴリゴリ手書きすることになります。 毎回毎回これではちょっと面倒なので、searchメソッドを呼び出すことで全文検索ができるModelを作ってみました。作ったとは言っても、一から作ったわけではなく、Django snippetsにあったtsverctorを操作するモデルにちょこっと手を入れただけですが。手を入れた部分はコメントで補足してあります。
[crayon lang="python"] # -*- coding: utf-8 -*- """ django.db.models.Model を拡張して、PostgreSQLのtsvector型に対応したモデルとマネジャを提供します。 http://djangosnippets.org/snippets/1328/ で公開されているコードを一部修正して使わせていただきました。 以下、原文のコメントです。 -------------------------------------------------- Support for full-text searchable Django models using tsearch2 in PostgreSQL. An example: from search import SearchableModel, SearchManager from django.db import models class TestModel (SearchableModel): name = models.CharField( max_length=100 ) description = models.TextField() # Defining a SearchManager without fields will use all CharFields and TextFields # objects = SearchManager() # You can pass a list of fields that should be indexed # objects = SearchManager( fields=('name','description') ) # You may also specify fields as a dictionary, mapping each field to a weight for ranking purposes # see http://www.postgresql.org/docs/8.3/static/textsearch-features.html#TEXTSEARCH-MANIPULATE-TSVECTOR objects = SearchManager( fields={ 'name': 'A', 'description': 'B', } ) # Create some test data. By default, the index field is automatically updated when save() is called. TestModel.objects.create( name='Model One', description='Hello world, this is a test.' ) TestModel.objects.create( name='Model Two', description='Testing, testing, one two three.' ) # You can force an index update to all or some instances: TestModel.objects.update_index() TestModel.objects.update_index( pk=1 ) TestModel.objects.update_index( pk=[1,2] ) # Perform a search with no ranking TestModel.objects.search( 'hello' ) # Perform a search that ranks the results, orders by the rank, and assigns the ranking # value to the field specified by rank_field TestModel.objects.search( 'test', rank_field='rank' ) """ from django.db import models class VectorField (models.Field): def __init__( self, *args, **kwargs ): kwargs['null'] = True kwargs['editable'] = False kwargs['serialize'] = False super( VectorField, self ).__init__( *args, **kwargs ) def db_type( self, connection=None ): # Django1.4対応のため、仮引数connectionを追加 return 'tsvector' class SearchableModel (models.Model): """ A convience Model wrapper that provides an update_index method for object instances, as well as automatic index updating. The index is stored as a tsvector column on the model's table. A model may specify a boolean class variable, _auto_reindex, to control whether the index is automatically updated when save is called. """ search_index = VectorField() class Meta: abstract = True def update_index( self ): if hasattr( self, '_search_manager' ): self._search_manager.update_index( pk=self.pk ) def save( self, *args, **kwargs ): super( SearchableModel, self ).save( *args, **kwargs ) if hasattr( self, '_auto_reindex' ): if self._auto_reindex: self.update_index() else: self.update_index() class SearchManager (models.Manager): def __init__( self, fields=None, config=None ): self.fields = fields self.default_weight = 'A' self.config = config and config or 'pg_catalog.english' self._vector_field_cache = None super( SearchManager, self ).__init__() def contribute_to_class( self, cls, name ): # Instances need to get to us to update their indexes. setattr( cls, '_search_manager', self ) super( SearchManager, self ).contribute_to_class( cls, name ) def _find_text_fields( self ): """ Return the names of all CharField and TextField fields defined for this manager's model. """ fields = [f for f in self.model._meta.fields if isinstance(f,(models.CharField,models.TextField))] return [f.name for f in fields] def _vector_field( self ): """ Returns the VectorField defined for this manager's model. There must be exactly one VectorField defined. """ if self._vector_field_cache is not None: return self._vector_field_cache vectors = [f for f in self.model._meta.fields if isinstance(f,VectorField)] if len(vectors) != 1: raise ValueError( "There must be exactly 1 VectorField defined for the %s model." % self.model._meta.object_name ) self._vector_field_cache = vectors[0] return self._vector_field_cache vector_field = property( _vector_field ) def _vector_sql( self, field, weight=None ): """ Returns the SQL used to build a tsvector from the given (django) field name. """ if weight is None: weight = self.default_weight f = self.model._meta.get_field( field ) return "setweight( to_tsvector( '%s', coalesce(\"%s\",'') ), '%s' )" % (self.config, f.column, weight) def update_index( self, pk=None ): """ Updates the full-text index for one, many, or all instances of this manager's model. """ from django.db import connection # Build a list of SQL clauses that generate tsvectors for each specified field. clauses = [] if self.fields is None: self.fields = self._find_text_fields() if isinstance( self.fields, (list,tuple) ): for field in self.fields: clauses.append( self._vector_sql(field) ) else: for field, weight in self.fields.items(): clauses.append( self._vector_sql(field,weight) ) vector_sql = ' || '.join( clauses ) where = '' # If one or more pks are specified, tack a WHERE clause onto the SQL. if pk is not None: if isinstance( pk, (list,tuple) ): ids = ','.join( [str(v) for v in pk] ) where = " WHERE \"%s\" IN (%s)" % (self.model._meta.pk.column, ids) else: where = " WHERE \"%s\" = %s" % (self.model._meta.pk.column, pk) sql = "UPDATE \"%s\" SET \"%s\" = %s%s;" % (self.model._meta.db_table, self.vector_field.column, vector_sql, where) cursor = connection.cursor() cursor.execute( sql ) # cursor.execute( "COMMIT;" ) # TransactionMiddleware対応のため削除 # cursor.close() # TransactionMiddleware対応のため削除 def search( self, query, rank_field=None, rank_normalization=32, use_web_query=False ): # web_query対応のため引数追加 """ Returns a queryset after having applied the full-text search query. If rank_field is specified, it is the name of the field that will be put on each returned instance. When specifying a rank_field, the results will automatically be ordered by -rank_field. For possible rank_normalization values, refer to: http://www.postgresql.org/docs/8.3/static/textsearch-controls.html#TEXTSEARCH-RANKING """ # web_query対応のため修正 # ts_query = "to_tsquery('%s','%s')" % (self.config, unicode(query).replace("'","''")) if use_web_query: to_tsquery_string = "to_tsquery('%s',web_query('%s'))" else: to_tsquery_string = "to_tsquery('%s','%s')" ts_query = to_tsquery_string % (self.config, unicode(query).replace("'","''")) where = "\"%s\" @@ %s" % (self.vector_field.column, ts_query) select = {} order = [] if rank_field is not None: select[rank_field] = 'ts_rank( "%s", %s, %d )' % (self.vector_field.column, ts_query, rank_normalization) order = ['-%s' % rank_field] return self.all().extra( select=select, where=[where], order_by=order ) [/crayon]
呼び出す側では、searchメソッドに条件やrankに利用するフィールド名を渡すことで全文検索が実行できます。
[crayon lang="python"] result = Spam.objects.search(query='俺達 炎上', rank_field='rank', use_web_query=True) [/crayon]
`use_web_query`に`True`を渡すことで、textsearch_jaのweb_queryの機能を使うことができ、スペース区切りの各文字列をAND条件として扱ったり、`OR`で結ぶことでOR条件として扱うことができます。web_queryの詳細は[公式サイト](http://textsearch-ja.projects.pgfoundry.org/textsearch_ja.html#web_query)で。 また、searchメソッドだけではなく、別の条件を組み合わせることもできます。
[crayon lang="python"] result = Spam.objects.search(query='検索条件', rank_field='rank', use_web_query=True).filter(enable=True) [/crayon]
これでだいぶスマートにtextsearch_jaを扱うことができるようになりました。まぁ、95%以上のコードは俺じゃなくて[dcwatsonさん](http://djangosnippets.org/users/dcwatson/)が書いたんだけど。

2013/01/07(月)若獅子たちの始動

2013/01/07 22:46
ライオンズのルーキーたちが続々と始動しています。 * [【西武】ドラ1増田「新人王」の誓い - プロ野球ニュース : nikkansports.com](http://www.nikkansports.com/baseball/news/f-bb-tp0-20130106-1068681.html) * [【西武】ドラ3金子「けがをしない体を」 - プロ野球ニュース : nikkansports.com](http://www.nikkansports.com/baseball/news/f-bb-tp0-20130107-1069069.html) * [【西武】高橋厳選マンガ200冊と入寮 - プロ野球ニュース : nikkansports.com](http://www.nikkansports.com/baseball/news/f-bb-tp0-20130106-1068683.html) * [【西武】ドラ5佐藤「ダイヤのA」と入寮 - プロ野球ニュース : nikkansports.com](http://www.nikkansports.com/baseball/news/f-bb-tp0-20130106-1068682.html) * [【西武】水口「安眠マイ枕」持参で入寮 - プロ野球ニュース : nikkansports.com](http://www.nikkansports.com/baseball/news/f-bb-tp0-20130106-1068680.html) 増田、金子、高橋、佐藤、水口……よし、全員揃っているな。