如何在Zend Framework应用程序中存储用户上传的文件?

时间:2009-12-04 20:04:31

标签: php zend-framework web-applications file-upload

我正在创建一个涉及习惯性文件上传和检索(PDF,Word文档等)的Web应用程序。

以下是要求:

  • 需要能够在我的视图脚本中链接到它们,以便用户可以下载文件。
  • 文件不应为未登录的用户访问。

问题#1:我应该在哪里存储这些文件?

  1. 在文件系统上?

    • 这是否意味着uploads目录中的public目录?
    • 我还希望在对代码进行更改时,我可以轻松部署新版本的应用程序。目前我有一个流程,我将我的应用程序上传到一个标题为当前日期的目录(即2009-12-01),并创建一个标题为current的sym链接。这意味着uploads目录需要在此目录之外,因此可以在应用程序的所有不同版本之间共享,无论当时正在使用哪个版本。
  2. 在数据库中?

    • 我可以将文件内容存储在数据库中,并在用户请求下载时将其提供。这样可以保护我的应用程序中的文件免受非授权用户的影响,但是一旦我创建了一个控制器来完成这项工作,我就可以获得相同的结果。
    • 这将解决必须sym link的问题。
    • 但是,这可能是不好的做法(除了信息之外,还要将数据存储在数据库中)。
  3. 你认为我应该怎么做?

    解决方案:

    这是我最终做的事情:

    我部署了我的应用程序的新版本,并为我想要使用的版本创建符号链接:

    /server/myapp/releases/2009-12-15
    /server/myapp/current -> /server/myapp/releases/2009-12-15
    

    我在应用程序目录上创建了一个目录,并在我的应用程序中创建了该目录的符号链接:

    /server/uploads
    /server/myapp/current/application/data/uploads -> /server/uploads
    

    我在APPLICATION_UPLOADS_DIR文件中定义了/public/index.php变量:

    // Define path to uploads directory
    defined('APPLICATION_UPLOADS_DIR')
        || define('APPLICATION_UPLOADS_DIR', realpath(dirname(__FILE__) . '/../data/uploads'));
    

    在我的上传表单中使用此变量来设置要上传到的目录:

    <?php
    
    class Default_Form_UploadFile extends Zend_Form
    {
        public function init()
        {
            //excerpt
    
            $file = new Zend_Form_Element_File('file');
            $file->setLabel('File to upload:')
                ->setRequired(true)
                ->setDestination(APPLICATION_UPLOADS_DIR);
        }
    }
    

    在我的控制器中,我将文件重命名为独特的文件,因此不能被具有相同名称的文件覆盖。我在数据库中保存了唯一的文件名,原始文件名和mime-type,以便我以后可以使用它:

    public function uploadAction()
    {
        //...excerpt...form has already been validated
    
        $originalFilename = pathinfo($form->file->getFileName());
        $newFilename = 'file-' . uniqid() . '.' . $originalFilename['extension'];
        $form->file->addFilter('Rename', $newFilename);
    
        try {
            $form->file->receive();
            //upload complete!
    
            $file = new Default_Model_File();
            $file->setDisplayFilename($originalFilename['basename'])
                ->setActualFilename($newFilename)
                ->setMimeType($form->file->getMimeType())
                ->setDescription($form->description->getValue());
            $file->save();
    
            return $this->_helper->FlashMessenger(array('success'=>'The file has been uploaded.'));
    
        } catch (Exception $exception) {
            //error
        }
    }
    

    为了下载文件,我创建了一个下载操作,用于检索文件并在Noginn's SendFile Action Helper的帮助下将其发送给用户。

    public function downloadAction()
    {
        //user already has permission to download the file
        $this->_helper->layout()->disableLayout(); //won't work if you don't do this
        $file = $this->_getFileFromRequest();
        $location = APPLICATION_UPLOADS_DIR . '/' . $file->getActualFilename();
        $mimeType = $file->getMimeType();
        $filename = $file->getDisplayFilename();
        $this->_helper->sendFile($location, $mimeType, array(
            'disposition' => 'attachment',
            'filename' => $filename
        ));
    }
    

    要在视图脚本中提供下载链接,我将它们指向带有文件id param的下载操作:

    <a href="<?php echo $this->url(array(
        'controller'=>'files',
        'action'=>'download',
        'file'=>$file->id)); ?>"><?php echo $file->displayFilename; ?></a>
    

    就是这样!如果您对此方法有任何其他建议或批评,请发表您的答案/评论。

3 个答案:

答案 0 :(得分:2)

为了允许用户直接下载文件,有两个解决方案:

  • 将您的文件放在网站的子目录中(您已经在做什么),这是应用程序经常需要的
  • 将您的文件放在:
    • 另一个单独的虚拟主机,仅提供那些静态文件(没有版本控制)
    • 或者,我认为Apache Alias也会这样做 - 实际上 - Apache手册中的示例看起来有点像你的情况^^


关于版本化目录的第二点:我经常做的是:

  • 将文件存储在不在应用程序目录下的目录中(例如/var/www);说/var/media
  • 创建指向该目录的符号链接:
    • /var/www/media指向/var/media
    • 这可以通过ln -s /var/media /var/www/media
    • 之类的方式完成
  • 创建该符号链接非常快,每次有新版本的应用程序时都可以完成
    • 您可以将应用程序的多个版本都指向同一目录。

这很好用,易于部署,并且“欺骗”应用程序认为文件位于其子目录中 - 即使它不是物理上的情况。

答案 1 :(得分:1)

如Pascal建议的那样,将符号链接到Web根目录以外的目录是一个很好的解决方案。一种方法,特别是如果您需要对文件进行某种用户访问控制,将使用PHP脚本来提供这些文件。你可以有一个下载控制器/动作等,它将文件名作为参数,然后从web根目录之外的某个目录加载文件并输出。然后你还需要在文件本身之前发送一些标题,以便浏览器知道如何处理它。标题可以设置特定于文件类型,也可以只是通用的“八位字节流”,以强制浏览器显示下载对话框。

答案 2 :(得分:1)

您不必将其存储在应用程序树中 - 您可以将其存储在其他位置并配置您的Web服务器以将虚拟目录指向/上传到该位置(Apache中的别名,IIS中的虚拟目录 - 大多数Web服务器都拥有它) ,并将应用程序链接到/ uploads。你当然也可以使用符号链接。 拥有单独的目录也允许您共享它,以防您有多个服务器运行相同的应用程序。