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);

Symfony2で入れると便利なBundle

DoctrineFixturesBundle

データ投入用のBundle。インストール方法、使い方はDoctrineFixturesBundleを参照。また、ファンクショナルテストのデータロードに使うSymfony2にDoctrineFixturesBundleを導入してPHPUnitと連動させるも参考になる。

KnpPagenatorBundle

ページング処理をわりと簡単に実装できるBundle。インストール方法はGitHubのKnpPaginatorBundleを参照。

Twitter Bootstrapと連携する

Twitter Bootstrapと連携するようにするには出力されるHTMLを少し変えないといけないので、以下のように行う。

テンプレートファイルを用意

適当な位置(Foo/BarBundle/Resources/views/Pagination/pagination.html.twigあたり)に以下のようなテンプレートファイルを用意。

{# Foo/BarBundle/Resources/views/Pagination/pagination.html.twig #}
{% if pageCount > 1 %}
<div class="pagination">
    <ul>
        {% if first is defined and current != first %}
            <li class="first">
                <a href="{{ path(route, query|merge({'page': first})) }}">&lt;&lt;</a>
            </li>
        {% endif %}

        {% if previous is defined %}
            <li class="previous">
                <a href="{{ path(route, query|merge({'page': previous})) }}">&lt;</a>
            </li>
        {% endif %}

        {% for page in pagesInRange %}
            {% if page != current %}
                <li class="page">
                        <a href="{{ path(route, query|merge({'page': page})) }}">{{ page }}</a>
                </li>
            {% else %}
                <li class="active"><a href="#">{{ page }}</a></li>
            {% endif %}

        {% endfor %}

        {% if next is defined %}
            <li class="next">
                <a href="{{ path(route, query|merge({'page': next})) }}">&gt;</a>
            </li>
        {% endif %}

        {% if last is defined and current != last %}
            <li class="last">
                <a href="{{ path(route, query|merge({'page': last})) }}">&gt;&gt;</a>
            </li>
        {% endif %}
    </ul>
</div>
{% endif %}

parameters.ymlにテンプレートファイルの指定を書く

parameters:
    knp_paginator.template.pagination: FooBarBundle:Pagination:pagination.html.twig

SonataGoutteBundle

webスクレイピングライブラリのGoutteを組み込むためのライブラリ。GoutteはSymfony\Component\BrowserKit\Clientを継承しているので、コントローラのファンクショナルテストを書くときと同じような感じで外部アクセスが可能。インストール方法などはGitHubのSonataGoutteBundleのページを参照。

Symfony2高速化メモ

一人プロジェクトでSymfony2使ってたらこれから部署でもSymfony2使っていくような流れになってきたので自分用にメモを残していってみる。
とりあえずまずは高速化に関連する話題。

APCを有効化する

Symfony2使うならAPCはほぼ必須。

ApcUniversalClassLoaderに置き換えてみる

デフォルトではUniversalClassLoaderが有効になっているが、これはfile_existsを何度も呼ぶため遅い。
クラスの位置をAPCにキャッシュするApcUniversalClassLoaderが同梱されているため、こちらに入れ替える。
ただし、名前空間をいじったりしたときにキャッシュをクリアしないと問題が起きたりするので、開発中はUniversalClassLoaderでいいかも。
app/autoload.phpの上部を以下の様に編集。

&lt?php
//require __DIR__.'/../vendor/symfony/src/Symfony/Component/ClassLoader/UniversalClassLoader.php';
require __DIR__.'/../vendor/symfony/src/Symfony/Component/ClassLoader/ApcUniversalClassLoader.php';
use Symfony\Component\ClassLoader\UniversalClassLoader;
use Symfony\Component\ClassLoader\ApcUniversalClassLoader;
use Doctrine\Common\Annotations\AnnotationRegistry;

$loader = new UniversalClassLoader();
//$loader = new ApcUniversalClassLoader('some prefix');
//...

パフォーマンス | Symfony2日本語ドキュメント#キャッシュ Autoloader (ApcUniversalClassLoader など)の利用

DoctrineのCaching Driversを使う

クエリキャッシュ、メタデータキャッシュ、結果キャッシュを使う設定ができるので、これを有効にする。
それぞれarray,apc,xcache,memcacheのいずれかを選択可能。

doctrine:
    orm:
        auto_mapping: true
        metadata_cache_driver: apc
        query_cache_driver: xcache
        result_cache_driver:
            type: memcache
            host: localhost
            port: 11211
            instance_class: Memcache

Configuration Reference (current) - Symfony#Caching Drivers

クエリキャッシュを使うときはこんな感じ(多分)。

<php
//...
$em = $this->get('doctrine.orm.entity_manager');
$q = $em->('SELECT i FROM Foo:BarBundle:Item i);
$q->useResultCache(true, 3600, 'somecache_id');
$result = $q->getResult();
//...

Native SQL

Doctrine使ってると、プロファイラを見てると、たまにすさまじいSQLが発行されてるときもある。
そんなときに最後の手段として、SQLを直接書いてEntity Classにマッピングする方法。
15. Native SQL — Doctrine 2 ORM 2.1 documentation

マッピングすらしたくなければ、Entity Managerからコネクション取ってそれを使って直接Query投げてしまえばいい。

Assetic管理

AssetにYUI_compressorやClosure Compilerを適用して圧縮したりしたい場合app/config.ymlに以下のような感じで記述。CoffeeScript使いたいときとかもここに設定する。

assetic:
    debug:          %kernel.debug%
    use_controller: false
    filters:
         closure:
            jar: %kernel.root_dir%/java/compiler.jar
         yui_css:
            jar: %kernel.root_dir%/java/yuicompressor.jar
         yui_js:
            jar: %kernel.root_dir%/java/yuicompressor.jar

Varnish

使ったことないけどとりあえずメモ。
Varnish を使ってウェブサイトを高速化する方法 | Symfony2日本語ドキュメント