Yiiにファイルをアップロードする

Yiiで多くのプロジェクトを書いたので、ダウンロードしたファイルを操作するための便利なメカニズムについて考えました。 Yiiはこれらの目的のために一連のツールを提供していますが、単一のメカニズムはありません。 この記事では、Yiiでダウンロードしたファイルを集中処理するというアイデアを提案したいと思います。

タスクは次のとおりです。 ファイルと写真をサーバーにアップロードするための便利なメカニズム(必要なチェックを含む)、写真のサムネイルの作成、ファイルをダウンロードするためのimgタグとリンクの自動生成が必要です。 拡張機能には何も見つかりませんでした。 最も近い意味はアップロード拡張機能ですが、多くの必要な機能を提供しません。 だから私は自分で書くことにしました。 記事を出版する直前に、私は偶然、同じようなアイデアを見るレシピを見ました。

コード例は参照用であり、作業中のプロジェクトから挿入しましたが、混乱する可能性があります。 作業コードが必要な場合は、記事の最後にプロジェクトへのリンクがあります。 どうぞ!

最初の反復。 標準機能を補完します。


Yiiは何を提供しますか? まず、 CUploadedFileクラス。ダウンロードしたファイルに関する情報を提供し、サーバーに保存することができます。 次に、ダウンロードされたファイルをチェックするCFileValidatorバリデーター。 公式ドキュメントがファイルのダウンロードを推奨する方法は次のとおりです。

 //  class MyModel extends CActiveRecord { public $image; public function rules(){ return array( array('image', 'file', 'types'=>'jpg, gif, png'), ); } } //  class MyModelController extends CController { public function actionCreate(){ $modMyModel=new MyModel; if(isset($_POST['MyModel'])){ $modMyModel->attributes=$_POST['MyModel']; $modMyModel->id_image=CUploadedFile::getInstance($modMyModel,'image'); if($modMyModel->save()){ $modMyModel->id_image->saveAs('path/to/localFile'); //   ,     //   } } $this->render('create', array('model'=>$modMyModel)); } } //  <?php echo CHtml::form('','post',array('enctype'=>'multipart/form-data')); ?> ... <?php echo CHtml::activeFileField($modMyModel, 'image'); ?> ... <?php echo CHtml::endForm(); ?> 


このアプローチにはいくつかの欠点があります。

  1. フレームワークには、ファイルをダウンロードするための専用フォルダーがありません
  2. ファイルのアップロードは、コントローラーで毎回説明する必要があります
  3. 示されたアプローチはactionUpdate()転送できません。これは、呼び出しごとにファイルがロードされることを想定しているためです。 また、通常のプロパティと同様にファイルを操作すると便利です。モデルを作成するときに読み込み、必要に応じてモデルを変更するときに再起動します。
  4. ファイルへのその後のアクセスに関する推奨事項はありません。 ただし、 path/to/localFileはモデルプロパティに保存できます。


もちろん、これらの欠点については、自分のプロジェクトのコンテキストでのみ話している。 そして、ここに私が提供したいものがあります。

まず、ファイルを保存する場所を決めましょう。 私の意見では、ディレクトリ.../protected/data/files保存に最適.../protected/data/files 。 一般的に、作業中に作成されたファイルには、フォルダ.../protected/runtimeがあり.../protected/runtimeが、意味では、 dataディレクトリはこれらの目的により適しています。 ファイル名はランダムに生成され( uniqid() )、モデルプロパティ$modMyModel->id_imageに保存されます。次の段落でその方法を説明します。 確かに、このアプローチには落とし穴が1つありdata 。ブラウザからアクセスするためにdataディレクトリが閉じられます。 これに対処する方法-少し後で。 今後は、 readfile()readfile()てファイルを動的にダウンロードし、 assetsフォルダーに画像(より正確には、画像のサムネイルreadfile()を公​​開することをお勧めします。

フォルダとファイルの命名を整理しました。 次に、検証とロードを扱います。 フォームから始めましょう。 これをやってみましょう:

 <?php echo $modMyModel->id_image ?> <?php echo CHtml::activeFileField($modMyModel, 'id_image_file'); ?> 


そのため、ファイルがアップロードされているかどうかを確認します。 そして、ファイル自体はid_image_fileという名前でロードされます。 echo $modMyModel->id_image代わりに、ファイルまたはサムネイル画像をダウンロードするためのリンクを挿入できます。

今検証。 アイデアは次のとおりです。バリデーターは、 $_FILESダウンロードファイル$_FILESかどうかを確認する必要$_FILESます。 その場合、 CUploadedFileオブジェクトを作成し、それを$modMyModel->id_imageます。 次に$modMyModel->id_image標準的な方法$modMyModel->id_image検証します。 これを行うには、 DFileValidatorから継承した独自のDFileValidatorバリデーターを作成します。 そしてすぐに別のDImageValidatorから継承されたDFileValidator 、画像のデフォルトのファイルタイプを示します。

最後に、ファイルをロードしてモデルを保存します。 検証後、ダウンロードされたファイルは$modMyModel->id_imageにあり、 CUploadedFileの形式にCUploadedFileます。 ロードが特定のプロパティに関連付けられないようにするには、モデルを保存する前に、そのプロパティのいずれかがCUploadedFileクラスのオブジェクトであるかどうかを確認する必要があります。 これで、モデルは次のようになります。

 class MyModel extends DActiveRecord { public $id_image; public function rules(){ return array( array('id_image', 'DImageValidator'), ); } public function beforeSave() { foreach ($this->attributes as $key => $attribute) if ($attribute instanceof CUploadedFile) { $strSource = uniqid(); if ($attribute->saveAs(Yii::getPathOfAlias('application.data.files') . '/' . $strSource)) $this->$key = $strSource; } return parent::beforeSave(); } } 


imageプロパティは、意図的にid_imageプロパティに置き換えられました。 さらに、その理由は明らかです。

小計を要約するには

  1. すべてのファイルは、フォルダ.../protected/data/filesにランダムな名前で保存されます。
  2. ファイルのアップロードは、データベースに保存される前にモデルで実行されます。
  3. プロパティをファイルとしてマークするには、次のものが必要です。
    1. rules()モデルで、 DFileValidatorバリデーターをこのプロパティに割り当てます。
    2. フォームで、このプロパティの入力に'_file''_file'名前を変更します。

  4. actionCreate()およびactionUpdate()メソッドは変更しないでおくことができます。


2回目の反復。 データベースを接続します。


ファイルをアップロードする方法を見つけました。 しかし、彼に連絡する方法は明確ではありませんが。 imgタグのsrcパラメーターに何を書くのですか? ダウンロード用のファイルを提供する方法は? 私の意見では、Yiiモデルの機能を使用してファイルを操作すると便利です。 実際、モデルがダウンロードされた各ファイルに対応する場合、ダウンロードを含むすべての低レベルの操作をそのファイルに委ねることができます。 そして、プロパティ$modMyModel->id_imageにこのモデルの主キーを保存します(このプロパティの名前の本質は明確です)。 次に、 $modMyModelに対して、適切な関係を決定し、たとえば次のように記述することができます。

 //  MyModel: public function relations() { return array( 'image' => array(self::BELONGS_TO, 'DImage', 'id_image'), ); } //  : $modMyModel = new MyModel; echo $modMyModel->id_image->image($htmlOptions); //        img echo $modMyModel->file->downloadLink(); //      


さらに、モデルを使用すると、元のファイル名を保存する問題(保存すると失われます)が自動的に解決されます。

行こう

フィールドid, source, name tbl_filesテーブルを作成します。 このテーブルに関連付けられたDFileモデルを定義します。 静的なupload方法を定義します:

 class DFile extends DActiveRecord { public $uploadPath; //     public function init() { $this->uploadPath = Yii::getPathOfAlias('application.data.files'); } public static function upload($objFile) { $modFile = new DFile; $modFile->name = $objFile->name; $modFile->source = uniqid(); if ($objFile->saveAs($modFile->uploadPath . '/' . $modFile->source)) if ($modFile->save()) return $modFile; return null; } } 


そしてすぐに空のDImage extends DFile作成し、 DImage extends DFileます。 後で必要になります。
次に、モデルを少し変更しましょう。 画像との約束された接続を定義し、 beforeSave()メソッドをbeforeSave()調整しbeforeSave()

 class MyModel extends DActiveRecord { public $id_image; public function rules(){ return array( array('id_image', 'DImageValidator', 'allowEmpty' => true), ); } public function relations() { return array( 'image' => array(self::BELONGS_TO, 'DImage', 'id_image'), ); } public function beforeSave() { foreach ($this->attributes as $key => $attribute) if ($attribute instanceof CUploadedFile) { $modFile = DFile::upload($attribute); //   DFile $this->$key = $modFile->id; } return parent::beforeSave(); } } 


モデルの$modMyModelオブジェクトでは、 $modMyModel->image介してファイルにアクセスできます。 それを有益に使用する方法-読んでください。

3回目の反復。 アップロードされたファイルへのアクセス。


この瞬間まで、私たちはファイルと写真をほとんど共有しませんでした。 実際、それらのダウンロードは同じです。 唯一の違いは、プリブートチェックです。 しかし、 DFileValidatorおよびDImageValidatorバリデーターはDFileValidatorうまく機能し、必要なすべてのルールを指定できます。

ダウンロードとは異なり、ダウンロードしたファイルや画像へのアクセス方法は異なります。 ファイルは、後でダウンロードできるようにロードされ、画像-それらを見るために。 ファイルから始めましょう。

ファイルを操作する

繰り返しますが、ファイルはダウンロードするためにダウンロードされます。 多くの場合、これにはアクセス権の確認が必要です。 合計で、2つのタスク、つまり、ダウンロードリンクの提供と、実際にはダウンロード用のファイルの発行を解決する必要があります。

リンク生成はDFile行うと便利DFile 。 このようなもの:

 class DFile extends DActiveRecord { public function downloadLink($htmlOptions = array()) { return CHtml::link($this->name, array('/files/file/download', 'id' => $this->id), $htmlOptions); } } 


リンクはFileControllerコントローラーをFileControllerます。 定義してください:

 class FileController extends DcController { public function actionDownload($id) { $modFile = $this->loadModel($id); header("Content-Type: application/force-download"); header("Content-Type: application/octet-stream"); header("Content-Type: application/download"); header("Content-Disposition: attachment; filename=" . $modFile->name); header("Content-Transfer-Encoding: binary "); readfile($modFile->uploadPath . '/' . $modFile->source); } public function loadModel($id) { $modFile = DFile::model()->findByPk($id); if($modFile === null) throw new CHttpException(404,'The requested page does not exist.'); return $modFile; } } 


ここではすべてが明確だと思います。 actionDownload()メソッドは追加のチェックを行いませんが、必要に応じて含めることができます。 現在のモデルでは、対応する関係を決定したら、 $modMyModel->file->downloadLink()記述できます。 もちろん、このアプローチはファイルへの直接リンクを発行するよりも生産性が低くなります。 パフォーマンスが重要な場合は、安全なdataディレクトリではなく、別の(開いている)ディレクトリでファイルを注文し、直接リンクを発行できます。

写真で作業する

写真では、状況はもう少し複雑です。 写真にはサムネイルが必要です。 さらに、写真では、ダイナミックリンクを作成する余裕はありません。 幸いなことに、Yiiは私たちの目的に使用できる便利なリソース公開メカニズムを提供します。 アイデアは次のとおりです。写真へのリンクを生成するときに、リソースとしてサムネイルを作成して公開します。 ただし、いくつかの問題があります。 まず、ソースイメージにアクセスする必要がある場合は、それをassetsフォルダーにコピーする必要もあります。 第二に、標準のYiiツールを使用してパブリケーションを実装することはできません。 実際には、公開されたファイルごとに、Yiiは独自のフォルダーを作成しますが、これはブルートフォースになります。 はい。標準ツールを使用して、 assetsフォルダにすぐにサムネイルを作成することはできません。

最初の問題は、大量の写真がダウンロードされておらず、元の写真へのアクセスが必要ない場合は関係ありません。 元の写真は高解像度で保存され、上記のメカニズムを使用してダウンロードできます。また、画面に表示するのにサムネイルのみが使用されます。 そのような問題が発生した場合、オプションとして、ソースフォルダーをassetsフォルダーにコピーすることはできませんが、リンクを作成できます(Yiiの標準公開メカニズムは、リンク作成を含む公開を提供します)。

2番目の問題については、出版物を自分で作成する必要があります。 しかし、それほど書くことはありません...

行きましょう。 DImageから継承されたDFileクラスが既にあります。 サムネイルの作成と公開について説明します。

 class DImage extends DFile { public $assetsPath; //      public $assetsUrl; // URL    public $thumbs = array( 'min' => array('width' => 150, 'height' => 150), 'mid' => array('width' => 250), 'big' => array('width' => 600), ); //   public function init() { $this->assetsUrl = Yii::app()->assetManager->baseUrl . '/files'; $this->assetsPath = Yii::app()->assetManager->basePath . '/files'; if (!is_dir($this->assetsPath)) mkdir($this->assetsPath); } //      $this->assetsPath public function getIsPublished() { foreach ($this->thumbs as $kThumb => $vThumb) if (!is_file($this->assetsPath . '/' . $this->source . '_' . $kThumb)) return false; return true; } //   public function publish() { if (!$this->isPublished) foreach ($this->thumbs as $kThumb => $vThumb) $this->createThumb($this->uploadPath . '/' . $this->source, $this->assetsPath . '/' . $this->source . '_' . $kThumb, $kThumb); return $this->assetsUrl . '/' . $this->source; } //   function createThumb($strSrcFile, $strDstFile, $strThumb) { //    $strSrcFile,   $strDstFile } } 


サムネイルを公開するには、 assetsフォルダーにfilesサブフォルダーを作成することをお勧めします。 assetsフォルダーを定期的にクリーニングすることをお勧めしますが、毎回assets/filesフォルダーの存在を確認する必要があります。 必要に応じて作成します。 サムネイル名は、サムネイル名で補足された画像名と同じです。 すべてのサムネイルが配置されている場合、画像は公開されていると見なされます。 ダウンロードしたファイルは変更できないため、ソースファイルと公開ファイルの日付を確認することは意味がありません。 publish()関数は、公開された画像のURLを返します(ただし、サムネイルは指定しません)。これは、Yiiでリソースを公開するという考え方と矛盾しません。

最後に、アップロードされた画像へのアクセスを検討します。 DImageクラスにimage()メソッドをDImage

 public function image($strThumb = 'min', $htmlOptions = array()) { return CHtml::image($this->publish() . '_' . $strThumb, $this->name, $htmlOptions); } 


これで、ファイルとの類推により、モデルに$modMyModel->image->image()を書くことができます。 ちなみに、サムネイルのサイズを突然変更する必要がある場合、または新しいサムネイルを追加する必要がある場合(一度私に起こったことがあります)、すべてのファイルが既にアップロードされている場合は、設定のサイズを変更してアセットフォルダーをクリアするだけで十分です。

最後の仕上げ


すべてが機能します。 写真がアップロードされ、表示されます。 ファイルがアップロードおよびダウンロードされます。 コードを少しとかすことが残っています。 たとえば、beforeSave()メソッドをMyModelクラスからDActiveRecordクラスに削除すると、そこからすべてのモデルが継承されることがわかります。 さらに、ファイルおよび画像の入力の表示は、 DActiveForm extends CActiveForm転送できます。 設定の保存は、 filesモジュールに割り当てることがfilesます。

まあ、そして良い伝統によると、 作業プロジェクトをダウンロードするためのリンクです。 多数の依存関係があるため、個別のファイルをアップロードするのは問題があることが判明したため、プロジェクト全体を投稿します。 データベースダンプは、protected / data / dump.sqlにあります。 設定から-Yiiへのパスを指定し、データベースへのアクセスを登録します。 基本クラスとバリデーターはprotected / componentsフォルダーにあり、ファイルに関するすべてはfilesモジュールにあります。

おわりに


したがって、出力は次のとおりです。

  1. 一元化されたファイルのアップロードとストレージ管理
  2. モデルにファイルプロパティを作成するための使いやすいインターフェイス
  3. ファイルとIMGタグをダウンロードするための高レベルのリンク生成


アイデアを発展させることができます。 たとえば、ほとんどすべてのWYCIWYGエディターは、ファイルと画像をダウンロードするためのインターフェイスを提供します。 これを行うには、ダウンロードアドレスを指定するだけです。 FileControllerダウンロードハンドラーを含めることができます。 しかし、サムネイルを公開する方法は?

または、記事に記載されている提案を使用して、サーバーに画像を安全にアップロードできます。 パート1およびサーバーへの画像の安全なアップロード。 パート2 上記のアップロード拡張機能を補足できます。 機能を動作に移行できます。

つまり、提案されたソリューションの準備ができていると考えるのは時期尚早です。 しかし、説明したアイデアが有用であることが判明した場合、作業を完了し、対応する拡張機能を公開する準備ができています。 読んでくれた皆さん、ありがとう!

Source: https://habr.com/ru/post/J156293/


All Articles