PHP5.3.3(windows)のgo-pear.batが動かねぇ

わけあってPHP5.3.3なんていう過去の遺物を使っているのだけど、PEAR使おうと思ったらgo-pear.batが動かなかったのでメモ。

これを参考に、

@ECHO OFF
set PHP_BIN=php.exe
%PHP_BIN% -d output_buffering=0 PEAR\go-pear.phar
pause

これを

@ECHO OFF
set PHP_BIN=php.exe
%PHP_BIN% -d output_buffering=0 -d phar.require_hash=0 PEAR\go-pear.phar
pause

に書き換えてあげる必要がある。

Symfony2のasset内のcss等から画像のパスを解決する方法が欲しい

Symfony2でBundle内のResources/public/cssなどに配置されたcssやjsからResources/public/images/内の画像等を使用する場合のスマートな方法って無いんでしょうか?

Railsだと、http://guides.rubyonrails.org/asset_pipeline.html にscssやjsなどから指定するための方法が解説されてるんですが、同じような指定ができると嬉しいのですが。

Solr3.6の日本語形態素解析エンジン Kuromoji

昔は形態素解析分かち書きしたい場合、Senを使ってやるのが普通だったと思うんだけど、3.6からはKuromojiという形態素解析エンジンが組み込まれているらしい。そしてexampleには最初からtext_jaというフィールドタイプが組み込まれており、すぐに試せる状態になっている。example/solr/conf/schema.xmlの726行からがtext_jaの定義。

KuromojiにはNormal, Search, Extendedの3種類のモードがあり、Normalは通常の形態素解析、Searchは複合語をなるべく短い単語ごとに分割するモード、ExtendedはSearchモードをベースに、未知語をuni-gramに分割するモードらしい。schema.xmlを見ると、デフォルトではSearchモードに設定されている。

どのような形で分かち書きされるかは、Solr AdminのAnalysys http://localhost:8983/solr/admin/analysis.jsp から試せる。

Extendedモードは辞書にない用語、例えば「きゃりーぱみゅぱみゅ」は「きゃ|り|ー|ぱ|み|ゅ|ぱ|み|ゅ」に分かち書きしてくれるし、なかなか良さそうな気がする。

Solrを動かしてみる

aptから入れる場合

とりあえず手軽に試してみたければ、Ubuntu系OSの場合aptを使って

$ sudo apt-get install solr-common solr-jetty

もしくはTomcatがよければ

$ sudo apt-get install solr-common solr-tomcat

でインストールできると思われる。でもバージョンが 1.4.1なので、今回は最新版(3.6.0)を落としてきて使うことにする。

aptを使わないで動かしてみる場合

まず、インストール前にjavaが入っているか確認。入っていなければ入れる。OpenJDKでもOracleJDKでもいいと思う。

$ java -version
java version "1.6.0_24"
OpenJDK Runtime Environment (IcedTea6 1.11.1) (6b24-1.11.1-4ubuntu3)
OpenJDK Server VM (build 20.0-b12, mixed mode)

1. Solrのパッケージ(3.6.0)をダウンロード

Solr公式ページhttp://lucene.apache.org/solr/を開き、

右側のDownloadリンクhttp://www.apache.org/dyn/closer.cgi/lucene/solr/3.6.0をたどる。

適当なミラーを選択する。

(今回は理研http://ftp.riken.jp/net/apache/lucene/solr/3.6.0/

ブラウザからダウンロードするか、もしくはリンクをコピーしてコンソールからダウンロード

$ mkdir packages/solr
$ cd packages/solr
$ wget http://ftp.riken.jp/net/apache/lucene/solr/3.6.0/apache-solr-3.6.0.tgz
$ #これから中を見てみるかもしれないのでソースも落としておく
$ wget http://ftp.riken.jp/net/apache/lucene/solr/3.6.0/apache-solr-3.6.0-src.tgz
$ tar xf apache-solr-3.6.0.tgz
$ #適当な場所に移動
$ mv apache-solr-3.6.0 ~/path/to/dir/
$ cd ~/path/to/dir/apache-solr-3.6.0/

2. 動作確認

$ cd example
$ java -jar start.jar
...
2012-06-16 10:54:05.667:INFO::Started SocketConnector@0.0.0.0:8983

8983番で起動しているようなので、アクセスしてみる。

ブラウザでSolr Admin http://localhost:8983/solr/admin/を開く。ページが開ければ正常。

3. テストデータを入れる

まずは同梱されているテストデータを入れてみる。Solrを起動したまま以下の操作を実行。

$ cd exampledocs
$ ./post.sh *.xml

4. 検索してみる

まずは全項目。Query Stringを*:*のまま実行。とりあえず色々結果が返却される。全件該当するはず。

次に絞り込み。Query Stringをname:iPodとして実行。nameフィールドに"iPod"が含まれるもののみ返却されるはず。

ひとまずこんな感じ。次は日本語、特に形態素解析を使った構成やプラグインの書き方について調べてみようかと思う。

anarchy golf (multi_key sort)

30.multi_key sort。部分適用でComparator作ってsort。Scalaで1位(他に誰もやってない)。しかしLanguage Rankingは30位…。

val s=io.Source.stdin.getLines.toList
def f(k:List[Int],x:Array[String],y:Array[String]):Boolean={val a=k.head
if(x(a)==y(a))f(k.tail,x,y)else x(a)<y(a)}
s.tail.map(_.split(' ')).toList.sort(f(s.head.split(' ').toList.map(_.toInt-1),_,_))map{a=>println(a mkString " ")}

anarchy golf

anarchy golfRuby,Perl,Haskellなんかの変態の集まる言語では歯が立たないのでScalaでまったりと。

18.Square rootScalaで。最初まじめにやってたけど決めうちを多用してまじめに変換するロジックをバッサリ消し去ったら大幅に短縮された。悲しい。まだまだ功夫が足りない。

var x=""
while({x=readLine;x!=null})println(if(x=="0x123")"17.058722109"else if(x=="0x123.fed")"17.087871761"else if(x=="+inf")"inf"else if(x=="-inf"|x=="NaN"|x=="-1")"nan"else"%.9f" format math.sqrt(x.toDouble))

22.ExpressionScalaで。

var l=readLine.split(" ").toList.map(_.toInt)
var a=List(l(0)-l(1))
for(i<-l.drop(2))a=a.map(_+i):::a.map(_-i)
a.distinct.sort(_<_)foreach println

sortはdeprecatedなので警告が出るけどsortWithより短いので。

Symfony2でビジネスロジックを大量にコントローラに書かない構成

気がつくとコントローラに大量のビジネスロジックが含まれるようなプロジェクトがよくあるけれども、複数の場所に同じロジックが存在していたりコントローラの見通しが悪くなったりするので望ましい状況ではない。そんな状況に陥らないようにするための構成。

EntityRepositoryを使う

以下のようなEntityクラスを仮定する。

<?php
// Entityクラス
namespace Foo\BarBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Foo\BarBundle\Repository\UserRepository;

/**
 * @ORM\Entity
 * @ORM\Table(name="user_table")
 * @ORM\Entity(repositoryClass="Foo\BarBundle\Repository\UserRepository")
 */
class User{

    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(type="boolean")
     */
    private $locked = false;
    
    /**
     * @ORM\Column(type="boolean")
     */
    private $enabled = true;
    
    // ゲッタ、セッタ面倒なので書くの省きます。以下にあると思ってください。
    // ...
}

有効なユーザ(lockedがfalse, enabledがtrue)を探すには、以下のようなコードになる。

$dql = <<<DQL
SELECT
    u
FROM
    FooBarBundle:User u
WHERE
    u.locked = false
    AND u.enabled = true
ORDER BY u.id
DQL;
$users = $em->createQuery($dql)
  ->getResult();

// もしくは
$users = $em->getRepository('FooBarBundle:User u')
  ->findBy(array(
    'locked' => false,
    'enabled' => true,
  ));

これぐらいだとコントローラに書いてしまってもいいかな、という程度の条件だけど、もっと条件が複雑になってきたときにいろんなところに毎回同じ処理を書きたくはないし、コントローラに複雑な処理を書くのはいかがなものかと思う。そこで、共通の処理をくくりだせるようになっているのがRepositoryクラス。

上記のEntityクラスに含まれるアノテーション@ORM\Entity(repositoryClass="Foo\BarBundle\Repository\UserRepository")がRepositoryクラスの指定。

下記のような感じでRepositoryクラスを定義。

<?php
// Repositoryクラス
namespace Foo\BarBundle\Repository;

use Doctrine\ORM\EntityRepository;
use Foo\BarBundle\Entity\User;

class UserRepository extends EntityRepository
{
    public function createSearchEnabledUsersQueryBuilder()
    {
        $dql = <<<DQL
SELECT
    u
FROM
    FooBarBundle:User u
WHERE
    u.locked = false
    AND u.enabled = true
ORDER BY u.id
DQL;
        return $this->getEntityManager()
            ->createQuery($dql);
    }
    
    public function searchEnabledUsers()
    {
        return $this->createSearchEnabledUsersQueryBuilder()
            ->getResult();
    }
}

これで、以下のようにsearchEnabledUsersを呼び出すことができるようになる。

$users = $em->getRepository('FooBarBundle:User u')
  ->searchEnabledUsers();

ビジネスロジックを書くためのサービスを定義する

たとえば複数のテーブルからデータを取得して、それを使ってinsertしたり外部サービスに送信したり、などというような複雑な処理があるとして、それを書くための場所は標準構成では特に定められていないけれど、Modelを定義するための場所を独自に作って、そこに書いてしまうのがいいのかな、と思ってる。たとえば、Foo\BarBundle\Manager以下にUserManagerを定義してみる。

<?php

namespace Foo\BarBundle\Manager;
use Foo\BarBundle\Entity\User;

class UserManager
{
    private $encoderFactory;
    private $entityManager;
    
    public function setEncoder($encoderFactory)
    {
        $this->encoderFactory = $encoderFactory;
    }
    
    public function setEntityManager($entityManager)
    {
        $this->entityManager = $entityManager;
    }
    
    public static function get($encoderFactory, $entityManager)
    {
        $obj = new UserManager();
        $obj->setEncoderFactory($encoderFactory);
        $obj->setEntityManager($entityManager);
        return $obj;
    }
    
    public function registerUser($info)
    {
        $user = new User();
        
        // なんかいろいろする
        $user->setSalt(md5(rand(10000,99999).$info->username));
        $encoder = $this->encoderFactory->getUser($user);
        $user->setPassword($encoder->encodePassword($info->password, $user->getSalt()));
        
        // ...
        
        $this->em->persist($user);
        $this->em->flush();
        
        return $user;
    }
}

これを使えるようにするためには、Resources/config/services.ymlにサービス登録の設定を追加する必要がある。

parameters:
    foo_bar.user_manager.class:  Foo\BarBundle\Manager\UserManager

services:
    foo_bar.user_manager:
        class:  %foo_bar.user_manager.class%
        factory_class:  %foo_bar.user_manager.class%
        factory_method: get
        arguments:
            encoder: @security.encoder_factory
            entityManager: @doctrine.orm.entity_manager

コントローラから使うときは以下のような感じで。

// ユーザ登録
$userManager = $this->get('foo_bar.user_manager');
$user = $userManager->registerUser($data);