ラベル Symfony2 の投稿を表示しています。 すべての投稿を表示
ラベル Symfony2 の投稿を表示しています。 すべての投稿を表示

2014年6月5日木曜日

symfony2でCSV出力(ダウンロード)する

symfony2シリーズ第3弾です。
CSV出力、何かと言われて実装することが多いのではないでしょうか。
FuelPHPではRESTコントローラがCSV対応しており、配列渡すだけなので久しぶりに実装しました。
と言っても良くある実装なのでsymfony2でActionで呼ばれた時の場合でご紹介します。

public function exportAction()
    {
        $header = [
            'ID',
            '名前',

        ];
        $list[] = $header;
        //DBから呼び出した場合はgetArrayResultを使うか自分で$dataを整形して下さい。
        $list[] = [1,'hoge'];
        $list[] = [2,'fuga'];

        $csv = $this->convertArrayToCsv($list);

        $response = $this->render(
                "AcmeSampleBundle::export.html.twig", [
            'csv' => $csv,
                ]
        );

        //Excel対策でUTF-8からSJIS-winに変換
        $contents = mb_convert_encoding($response->getContent(), 'SJIS-win', 'UTF-8');

        //headerのSET
        $response->headers->set('Content-Type', "application/octet-stream; name=hoge.csv");
        $response->headers->set('Content-Disposition', "attachment; filename=hoge.csv");
        $response->setContent($contents);

        return $response;
    }


    //配列をCSVに変換。
    //文字列のエスケープをしてくれるのでfputcsv()を利用
    private function convertArrayToCsv($list)
    {
        $fp = fopen('php://temp', 'r+b');
        foreach ($list as $fields) {
            fputcsv($fp, $fields);
        }
        rewind($fp);
        $tmp = str_replace(PHP_EOL, "\r\n", stream_get_contents($fp));
        return $tmp;
    }

表示するTwig側(例ではexport.html.twig

{{ csv|raw }}

としてます。
rawを使わないとcsv内のクォートなどをエスケープするので忘れないでください。

CSVから配列にする場合のメソッドはこちらです。

PHPでCSVから配列を作る



2014年5月8日木曜日

Symfony2でDoctrine2使う時によく使うEntityのアノテーション一覧とDoctrine2のコマンド一覧

Symfony2シリーズ第二弾です。
Doctrine2はアノテーションでEntityの関係や属性を指定出来るので便利です。
ただドキュメントが英語だったので自分用によく使うヤツをまとめて置いときます。

公式ドキュメント(アノテーション一覧)

/**
 * UNIQUE制約のIndexを作るとき
 * nameを指定しない場合はIDX_6E72A8C13B66675Bのように種類_ランダムの名前が付けられる
 * @ORM\Table(name="テーブル名", uniqueConstraints={@UniqueConstraint(name="キー名", columns={"カラム名"})})
 * 複数指定もできる
 * @ORM\Table(name="テーブル名", uniqueConstraints={@UniqueConstraint(columns={"カラム名", "カラム名"})})
 *
 * Indexを指定したい時
 * @ORM\Table(name="テーブル名", indexes={@index(columns={"カラム名"})})
 * 複数指定もできる
 * @ORM\Table(name="テーブル名", indexes={@index(columns={"カラム名", "カラム名"})})
 *
 **/
class テーブル名
{
    /**
     * ID
     *
     * @var integer
     * 
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * オートインクリメントの指定
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * ほげ(外部キー貼る側)
     * 外部制約(リレーションを指定するときは名前空間を指定すると補完が効く
     * @var \project\MyBundle\Entity\Hoge\Hoge
     * リレーションの指定
     * @ManyToOne(targetEntity="\project\MyBundle\Entity\Hoge\Hoge", inversedBy="hoge")
     * @JoinColumn(name="hoge_id", referencedColumnName="id")
     * */
    private $hoge;

    /**
     * ほげ(外部キー貼られる側)
     * 外部キー貼られる側も指定がいる
     * @var \Doctrine\Common\Collections\ArrayCollection
     * カスケードの動作等は貼られる側で指定する
     * @OneToMany(targetEntity="\project\MyBundle\Entity\Hoge\Hoge", mappedBy="hoge", cascade={"persist","remove"})
     * */
    private $hoge;
 
     /**
     * 登録日時
     *
     * @var \DateTime
     *
     * @Gedmo\Timestampable(on="create")
     * @ORM\Column(name="created_at", type="datetime")
     */
    private $createdAt;

    /**
     * 更新日時
     *
     * @var \DateTime
     *
     * @Gedmo\Timestampable(on="update")
     * @ORM\Column(name="updated_at", type="datetime")
     */
    private $updatedAt;
}

注意が必要なのはリレーションを定義するときは両方のEntityにアノテーションを記載する必要があります。
そしてSQL的にはカスケードの条件は外部キーを貼る側に記載しますがアノテーションは外部キーを貼られる側に記載します。
というよりも実際にカスケードの条件はDDLとしては付与されてません。
アノテーションでのカスケード処理はDoctrineが処理する際に行うだけで実際のDBのDDLには関与しないので注意が必要です。
同じようにdefaultやon="create"も同様です。
DDLとしてdefaultやtimestamp型が使われるわけではないのです。
このへんはDBとコードが不一致なので動作については注意が必要ですね。

それとDoctrine2のよく使うCUIコマンドです。

//DBの作成
php app/console doctrine:database:create

//エンティティの作成
php app/console doctrine:generate:entity --entity="MyBundle:Entity名" --fields="name:string(255) price:float description:text"

//ゲッター・セッターの作成
php app/console doctrine:generate:entities project名/MyBundle/Entity/エンティティ名

//全部のエンティティの作成
php app/console doctrine:generate:entities projectMyBundle

//テーブルの作成
php app/console doctrine:schema:update --force

//データの投入
php app/console doctrine:fixtures:load --env=test


Doctrine2でDBマイグレーションを管理すると環境の複製やリリース等が簡単になります。
またFuelPHP2.0でもDoctrineを採用するようです。
PHPフレームワークのDBのスタンダードになりつつあるのでDoctrine2を覚えておいて損はないと思います。
(NetBeansはDoctrine2対応していますしね!)


ということで今日のところは以上です。

Symfony2のTwigExtentionをからContainerやEntityManagerを呼び出す

Symfony2は重厚で大規模開発には非常に優秀だと思います。
そんなSymfony2ですがちょっと手の込んだ事を調べると日本語情報が少ない印象です。
英語の公式ドキュメントとstackoverflow.comを行ったり来たりした結果をメモとして残しておきます。

やりたいこと


  1. Symfony2でTwigExtentionを作る
  2. 作ったTwigExtentionのgetGlobals()からDBの呼び出しやサービスの呼び出しをする
  3. base.html.twigの変数にDBから取り出した値を使う。

この2.をするのに情報がなかったです。
1.については公式ドキュメントがありますのでそちらを参考にしてください。

カスタムTwig拡張の書き方


さて本題ですがTwigExtentionをサービスコンテナに登録しましょう。
その際に

# src/Acme/DemoBundle/Resources/config/services.yml
services:
    acme.twig.acme_extension:
        class: Acme\DemoBundle\Twig\AcmeExtension
        tags:
            - { name: twig.extension }

    arguments:
            em: "@doctrine.orm.entity_manager"
            container: "@service_container"

とargumentsとしてEntityManagerとContainerを渡します。
次にTwigExtentionクラスのコンストラクタに
public function __construct($em, $container)
    {
        $this->em = $em;
        $this->container = $container;
    }
と記載します。
あとはControllerと同様に各メソッドからDoctrine2のmodelを呼び出すだけです。
実際にbase.html.twigの初期値にDBから取り出したデータを使いたいときはgetGlobals()をオーバーライドします。
public function getGlobals()
    {

        $var = $this->container
                ->get('my_domain.hoge_repository')
                ->getHoge();
        return [
            'hoge' => $hoge,
            'fuga' => 'fuga',
        ];
    }
と言った感じです。
base.html.twigの拡張すればかなりDRYにすることが出来るます。
ただしTwigExtentionでDBを呼び出すと毎回呼ばれます。
ですので出来るだけ回数を減らす必要があると思います。

Symfony2シリーズ続くかわかりませんが現場からは以上です。