anarchy golf
anarchy golf。Ruby,Perl,Haskellなんかの変態の集まる言語では歯が立たないのでScalaでまったりと。
18.Square rootをScalaで。最初まじめにやってたけど決めうちを多用してまじめに変換するロジックをバッサリ消し去ったら大幅に短縮された。悲しい。まだまだ功夫が足りない。
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))
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})) }}"><<</a> </li> {% endif %} {% if previous is defined %} <li class="previous"> <a href="{{ path(route, query|merge({'page': previous})) }}"><</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})) }}">></a> </li> {% endif %} {% if last is defined and current != last %} <li class="last"> <a href="{{ path(route, query|merge({'page': last})) }}">>></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の上部を以下の様に編集。
<?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日本語ドキュメント