読者です 読者をやめる 読者になる 読者になる

sudoersの挙動

Linux

CentOS5系と6系によってsudoersの挙動が異なることが発覚したのでメモ。

sudo経由で色々コマンドを実行する際にパスが通っていないと困るので、
env_keepに環境変数のPATHを追加することによって、sudoでコマンドを実行した際にも問題ないように動作させていた。

これがCentOS6になることで、env_keepにPATHを追加するだけでは駄目だった…

CentOS6ではsecure_pathがデフォルトで有効になっているらしく、
secure_pathが有効になっている場合はenv_keepにPATHを追加しても、secure_pathで指定されているパス以外シカトされるらしい。

なので、CentOS5と同様の動作をさせるには、secure_pathを無効にする必要がある。

※sudo経由で実行しているバッチが動作していないことによって発覚…

参考記事 : http://www.maepachi.com/blog/entry?id=128

Symfony2で動的値を自動でテンプレートにアサインする

PHP Symfony2 Twig

タイトルの通り、サイトを作っているうえで動的値をテンプレートに自動的にアサインしたい事って結構あるんですよね。
例えば、ログイン機能がついているサイトではログイン中のユーザー情報とか。

Symfony2の場合、Controllerで毎回ログインしているユーザーを取得し、テンプレートに渡すのは非常に面倒です…
軽くググってみたけど、詳細な実装方法が記述されているページがすぐには見つからなかったので書いてみます。

ちなみに静的値をテンプレートに自動的にアサインしたい場合は下記に実装方法が載っています。


動的値の場合には上記ページにも記載されていますが、Twig Extensionを利用して実装します。
ログインしているユーザーをテンプレートに自動的にアサインする例を下記に書いてみます。

1. ログインユーザーを自動的にアサインするTwig Extensionクラスを作成

<?php

namespace Acme\DemoBundle\Twig\Extension;

use Symfony\Component\DependencyInjection\ContainerInterface;

class GlobalAssignExtension extends \Twig_Extension
{
    protected $container;

    /**
     * __construct
     *
     * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
     */
    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    /**
     * テンプレートにアサインする変数を設定
     *
     * @return array
     */
    public function getGlobals()
    {
        return array(
            'loginUser' => $this->getLoginUser(),
        );
    }

    /**
     * ログイン中のユーザーを取得
     *
     * @return mixed
     */
    protected function getLoginUser()
    {
        $token = $this->container->get('security.context')->getToken();

        if (is_null($token)) {
            return null;
        }

        return $token->getUser();
    }

    /**
     * Returns the name of the extension.
     *
     * @return string
     */
    public function getName()
    {
        return 'global_assign_extension';
    }
}

動的値をテンプレートに自動的にアサインするにはTwigExtensionを継承し、getGlobalsメソッドの返り値として配列を返すとKeyが変数名となりテンプレートで利用することが可能です。
上記のコードではDIコンテナを利用して、ログインしているユーザーを取得してきています。

このクラスを作成した後はサービスとして上記クラスを登録します。

2. 作成したTwig Extensionのサービス登録 (services.yml)

global.assign.twig.extension:
    class: Acme\DemoBundle\Twig\Extension\GlobalAssignExtension
    arguments: [ @service_container ]
    tags:
        - { name: twig.extension }

上記のarguments: [ @service_container ]の行はコンストラクタにコンテナを渡す設定です。
Twig Extensionのサービスを登録するうえで注意が必要なのは、必ずtwig.extensionのタグを指定して下さい。
これがないと自動的に呼ばれることはなく、テンプレートでも変数を利用することは出来ません。

実装は以上でここまで完了すると、どこのテンプレートでもloginUserという変数が利用可能です。

Symfony2.1によるmonologの設定

PHP Symfony2

あるアプリケーションをSymfony2.0系からSymfony2.1に移行していた時にmonologの設定で少しはまったので備忘録を。
Symfony2.0系では下記のようなmonologの設定をしていて、正常にdebug以上はログに記述し、error以上のログはメールで受け取れていた。

monolog:
    handlers:
        main:
            type:         fingers_crossed
            action_level: debug
            handler:      grouped
        # streamed, bufferedに渡す
        grouped:
            type:    group
            members: [streamed, buffered]
        # debug以上をログファイルに書き込み
        streamed:
            type:  stream
            path:  %kernel.logs_dir%/%kernel.environment%.log
            level: debug
        # error以上をメールする
        buffered:
            type:    buffer
            handler: swift
        swift:
            type:       swift_mailer
            from_email: %system_mail_from%
            to_email:   %log_mail_to%
            subject:    "エラーだよ!"
            level:      error

上記の設定のまま、Symfony2.1に移行するとメールが一切受け取れなくなった。
ソースを追ってみると、SwiftMailer自体にはログ情報は渡っているが、最終的に下記のソースの部分まで処理が行き送信自体されていない。

vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/MessageLogger.php

<?php
    ~

    /**
     * Invoked immediately after the Message is sent.
     *
     * @param Swift_Events_SendEvent $evt
     */
    public function sendPerformed(Swift_Events_SendEvent $evt)
    {
    }

しょうがないので、色々ネットで調べてみると下記の記事を見つけた。

In fact, the problem is a known issue and is present when using a buffered swift_mailer Monolog handler with a memory spool.
As a workaround, to avoid any problems, comment the "spool: { type: memory }" in config.yml

See the following threads:
https://github.com/Seldaek/monolog/issues/154
https://github.com/symfony/symfony-standard/issues/425

http://forum.symfony-project.org/viewtopic.php?t=47160&p=167193

結局、ここに書いてあるconfig.ymlのswiftmailerの設定でspool: { type: memory }をコメントアウトしたらちゃんと動いたのでよかった!

PHPでTCPサーバを作ってみる

PHP

この記事はPHP Advent Calender 2012の2日目の記事になります、詳細は以下をどうぞ。

PHP Advent Calender 2012

フレームワークCMS的な記事が多いので、あまり参考例のないTIPSを書きたいと思います。

PHPでTCPサーバを立ててみる

PHPでTCPなサーバを作るとなると、socket関数やfsockopenなどを使った例を多く見かけます。
PHPというよりかはLinuxなネタになってしまいますが、ここではxinetdを使った例を書いてみたいと思います。

xinetdとは?

今回は、スーパーサーバーと呼ばれる xinetd の設定方法について説明していきます。スーパーサーバーとは、ポート監視用のデーモンプログラムで、あるポートに対してアクセスがあると、設定ファイル (/etc/xinetd.d/ 等) を元にポートに対応したサービス (ftp 等) を起動します。この際、ポートとサービスの関係は、/etc/services によって導かれます。

http://www.express.nec.co.jp/linux/distributions/knowledge/network/xinetd.html

ということで、xinetdとは定義した設定ファイルを元にサービスを起動してくれるデーモンということです。
これを使ってPHPでサーバを立ててみます。

まずはxinetdの設定ファイルを作成します。
xinetdがLinuxに入っていない場合にはyum install xinetdを行えばインストールできます。

/etc/xinetd.d/php-server

# サービス名を指定
service php-server
{
    # 設定の有効無効を指定します、noに設定すると有効になる
    disable = no

    # サービスの種類を指定、/etc/rpc, /etc/servicesに記述のないサービスを扱うにはUNLISTEDを指定
    type = UNLISTED

    # ソケットの種類を指定します、ここではTCPサーバを扱うのでstreamを指定
    socket_type = stream

    # 使用するプロトコルを指定、/etc/protocols内に記述されたプロトコルを指定
    protocol = tcp

    # サービスを実行するユーザーを指定
    user = root

    # マルチスレッドの有効無効を指定、noの場合シングルスレッド
    wait = no

    # サービスで利用するポート番号を指定
    port = 10000

    # ポートにアクセスされた際に起動するプログラムを指定
    server = /srv/server.php
}

上記の通り、xinetd設定自体は非常に簡単です。
portで指定したポート番号はiptablesで開放しておきましょう。

次に「server = 〜」で指定したプログラムを作成します。

/srv/server.php

#!/usr/bin/php -q
<?php
$stdin = fopen('php://stdin','r');

while (true) {
    $buffer = trim(fgets($stdin, 4096));

    if ('exit' === $buffer) {
        break;
    }

    echo $buffer . "\n";
}

プログラム自体は非常に簡単で、入力を受け取り単純に入力された値を表示するだけのプログラムです。
exitが入力された際にはプログラムが終了します。
作成したプログラムには実行権限をつけておきます。

chmod +x /srv/server.php

ここまで作成したらxinetdを再起動します。

/etc/init.d/xinetd restart

以上で完了です、では接続してみましょう。
下記の画像のように入力したものが応答で帰ってきます、最後にexitを入力し終了させています。

f:id:ryster:20121203070204j:plain

非常に簡単にPHPを使ったTCPサーバーが作成できました。
あとは実装次第で色々なサーバを作ることが出来るでしょう、勉強のためにハニーポットなんかを作ってみるのもいいかもしれません。

Doctrine2.3がすごくよくなってる

Symfony2 Doctrine2 PHP

Symfony2.1を使っていて感動した!
Symfony2.0系ではformでEntityを利用した場合、同じform内に同一のEntityがあった場合でもデータは別途取得される仕様だった。

<?php
    $builder
            ->add('product1', 'entity', [
                'class' => 'Acme\\DemoBundle\\Entity\\Product',
            ])
            ->add('product2', 'entity', [
                'class' => 'Acme\\DemoBundle\\Entity\\Product',
            ])
            ->add('product3', 'entity', [
                'class' => 'Acme\\DemoBundle\\Entity\\Product',
            ]);

こんな感じでformを生成するとdoctrine2.1系では3つの同一のDQLを発行し、formにバインドする。
Symfony2.1(Doctrine2.3)系ではこれが1つのDQLのみ発行し、使いまわされるように効率化されている。

同じform内で同一のEntityを利用することはそんなにあるわけではないが、Doctrine2.1系のこういう部分を見るとほんといらっときていたので、個人的にはこれはかなり嬉しい変更点だった。

Twigでエンティティオブジェクトにアクセスする際の注意点

Twig Symfony2

Twigテンプレート内でエンティティオブジェクトにアクセスする際にforで回しながら処理したかったんですが、エラーが出て見事に少しハマった。
下記のようにtypesに入っている値を利用して、エンティティオブジェクトにアクセスするとエラーが出る。

{% for type in types %}
    {% set column = type ~ 'Status' %}
    {{ entity[column] }}
{% endfor %}

Twigのドキュメントを読んでみたところ、attribute関数というものを発見した。
http://twig.sensiolabs.org/doc/templates.html#variables

attribute関数を使って試してみるとうまくいった。

{% for type in types %}
    {% set column = type ~ 'Status' %}
    {{ attribute(entity, column) }}
{% endfor %}

なぜこれで動くのかは不明なのでattribute関数の中身を後で読んでみる。

SwiftMailerでRFC違反のメールアドレスでもメールを送信したい

Symfony2 Doctrine2

Symfony2の標準メールライブラリはSwift_Mailerが使われている。

メール送信を試していたが、どうも送信前にメールアドレスのチェックを行うらしくRFC違反しているメールアドレスだと例外が投げられて送れない…

例外が投げられる部分は下記の箇所

vendor/swiftmailer/lib/classes/Swift/Mime/Headers/MailboxHeader.php

  private function _assertValidAddress($address)
  {
    if (!preg_match('/^' . $this->getGrammar()->getDefinition('addr-spec') . '$/D',
      $address))
    {
      throw new Swift_RfcComplianceException(
        'Address in mailbox given [' . $address .
        '] does not comply with RFC 2822, 3.6.2.'
        );
    }
  }

とりあえずこのチェックを回避するオプションはないっぽいので、throwの部分をコメントアウトして強制的に送るしかないっぽい