スポンサーサイト

0

    一定期間更新がないため広告を表示しています


    • 2014.12.23 Tuesday
    • -
    • -
    • -
    • -
    • -
    • by スポンサードリンク

    Doctrineでファイルアップロードを扱う方法 がイケてない件 #symfony_ja

    0
      ユーザーにファイルをアップロードさせてサーバー側に保存する。非常によくある処理です。
      公式のSymfony cookbookには既にそのものズバリなタイトルのドキュメントがあります。

      How to Handle File Uploads with Doctrine
      http://symfony.com/doc/current/cookbook/doctrine/file_uploads.html
      日本語訳:Doctrine でファイルアップロードを扱う方法
      http://docs.symfony.gr.jp/symfony2/cookbook/doctrine/file_uploads.html

      日本語版は原文に比べて多少内容が古いですが、説明されている実装方法は同じです。
      最近、Symfony歴の浅いプログラマにSymfonyの使い方を説明する機会がなぜか多く、そのうち一人にファイルアップロードのケースを書いてもらおうと思ったのですが、上記ページを見せて説明していたら心中「なんか違う…」という違和感がフツフツと湧いてきたのです。

      まだまだsymfony1癖が抜けずSymfony2初心者だった頃の私にとっては「素晴らしい!」と思えたのに、なんでだろう?
      というわけで、どこが違和感(不便…メンテしづらい…もやもや…)なのか、今の私が同じ処理を書くとしたらどうなるか考えてみました。

      まず、公式の「Doctrine でファイルアップロードを扱う方法」がイケてないと思う点は、下記の2点です。(あくまで個人的に、です。)
      1. Doctrine2のLifecycleCallback(PrePersist, PreUpdate, PreRemove)を利用しているため、実際の処理(アップロードファイルが一時保存先から恒久的な保存先に移動)がいつ呼ばれるのかわかりにくい。

      2. アップロード先パスがEntityにベタ書きでテスト/変更しづらい。
      アップロード先のディレクトリ(Document::getUploadRootDir())を直接書かざるを得ません。特にサンプルコードのように__DIR__を使って書かれていると、Symfony2 standard editionの中に置いた状態(あるいは同じディレクトリ構造を無理やり作る)じゃないとテストが失敗してしまいます。せっかく再利用可能なバンドルを書いても単独でテストできないことになります。
      それに、ファイルの保存先を設定ファイルで指定することもできなくなります。一見、Document::setUploadRootDir()のようなメソッドと$uploadRootDirプロパティを設定すればいいじゃん、と思うかもしれません。でも、そうすると「エンティティのupload()メソッドを呼ぶ前には必ずsetUploadRootDir()を呼ばなければならない」という決まりが必要になるわけです。データ保存のタイミングで必ず呼ばれるDoctrineのLifecycleCallbackを使う意味が失われてしまいます(せっかくアプリケーション内のどこから$documentを保存してもファイルアップロード周りの処理が行われるようになっているのに、アプリケーション内のどこでも$documentを保存しようとしたらまず先にsetUploadRootDir()を呼ぶ必要が出てくる)。
      #こういう実装ルールって、後々うっかりミスでトラブルになる臭いがしませんか?

      では、「Doctrineでファイルアップロード」をどう実装するか?
      答え:専用のサービスを作りましょう

      アップロードしたファイルの扱いで何が一番面倒かと考えたとき、データベース上のレコードとファイルシステム上のファイルの同期ですよね。ファイル保存したのにレコード保存してない、とか。レコード削除したのにファイル削除してない、とか。
      それ「だけ」を担当するサービスを作ってしまえば、同期の心配はそこにお任せできるわけです。

      サービスで実装した版がこちら

      実際のアップロードファイル移動処理はサービスで行い、コントローラーからはサービスのメソッドを呼び出すだけです。
      ファイルの恒久的保存先が新規Documentの保存後じゃないと決まらない場合(例えば、auto_increment/serialなidを使って保存先ディレクトリを決定する場合。id=1のdocumentのファイルをuploads/documents/1/hoge.jpgやuploads/documents/1.jpgのように保存したいとき)も、処理の順番を書き換えればいいだけです。なんならpersist()→flush()→ファイル移動処理→パス(保存されたファイル名)を変更flush()と2回保存もできちゃいます。LifecycleCallbackのトリッキーな使い方をググる必要はありませんw
      見るからにテストも書きやすいです。
      #そもそも昨今、アップロードしたファイルの恒久的保存先が同じファイルシステム内とも限らない訳で、追加要件が出てきたときにサクッと変更できるのは圧倒的にサービス方式だと思います。

      まぁこれが正解とか公式が間違っているというわけではなく、こういう方法もあるよ(こっちのほうが私はしっくりくるよ)という話でした。


      スポンサーサイト

      0

        • 2014.12.23 Tuesday
        • -
        • 00:35
        • -
        • -
        • -
        • -
        • by スポンサードリンク

        コメント
        コメントする








           

        PR

        calendar

        S M T W T F S
              1
        2345678
        9101112131415
        16171819202122
        23242526272829
        30      
        << April 2017 >>

        twitter

        selected entries

        categories

        archives

        recent comment

        • 結局CodeIgniter用汎用Modelクラス&汎用CRUDスクリプトを書きました
          プログラマー
        • icu4.4以上が用意できないサーバーでSymfony2.3以上を使う方法
          よし
        • icu4.4以上が用意できないサーバーでSymfony2.3以上を使う方法
          ななうぇぶ
        • icu4.4以上が用意できないサーバーでSymfony2.3以上を使う方法
          よし
        • icu4.4以上が用意できないサーバーでSymfony2.3以上を使う方法
          よし
        • WindowsのPCで開発するphperがxhprofを使う方法
          ななうぇぶ
        • WindowsのPCで開発するphperがxhprofを使う方法
          川本
        • [バッドノウハウ]Symfony2で別テーブルの集計項目を一覧に含めたいとき
          よし
        • Symfony Advent Calendar JP 2012 day 14 - vendorをcomposerで管理しているプロジェクトにcomposerを使わずにバンドルを追加したときのautoloadの書き方
          77web
        • Symfony Advent Calendar JP 2012 day 14 - vendorをcomposerで管理しているプロジェクトにcomposerを使わずにバンドルを追加したときのautoloadの書き方
          ktz

        recent trackback

        • HTMLの表(TABLE)のセル(TD)に斜線を引くjavascriptライブラリ slash.js 作っちゃいました
          常山日記
        • django対symfony 日本語メール送信(その1 symfony編)
          CPA-LABテクニカル
        • CodeIgniterでユーザー認証
          されどLAMPな日々
        • 久々にdjangoを最新版にしたらHTMLがエスケープされちゃった!!(解決済み)
          常山日記
        • FastCGIを諦めてmod_pythonを使う。Apacheのアップグレード
          サーバー用語集
        • さくらインターネット、sqlite3でdjango@CGI版を使う際の設定メモ
          常山日記
        • さくらインターネット スタンダードプランでdjango使ってる方、DBは?
          mitszoの日記
        • python多次元リストをsort(並べ替え)する方法?
          mitszoの日記
        • フォームから送信した値とrequest.POSTの挙動($_POST@PHPとの比較)
          Humming Via Kitchen
        • 日本語テキストをtruncate@django(Python全般にも??)
          常山日記

        recommend

        links

        profile

        search this site.

        others

        mobile

        qrcode

        powered

        無料ブログ作成サービス JUGEM