【问题标题】:CakePHP 3.7 - Test case file uploadCakePHP 3.7 - 测试用例文件上传
【发布时间】:2019-06-14 18:21:48
【问题描述】:

如何在 CakePHP 3 中使用控制器测试用例测试文件上传功能?

我一直遇到 PHP 认为文件实际上没有上传的问题。适用于浏览器测试但不适用于测试用例的验证规则:

                ->add('file', [
                    'is_uploaded_file' => [
                        'rule' => ['uploadedFile', ['optional' => false]],
                        'message' => 'File is no valid uploaded file'
                   ],

我很快发现is_uploaded_filemove_uploaded_file 在单元测试中是不可能被愚弄的。

然而,这方面的大多数主题都是旧的和/或不是专门针对 CakePHP 的,所以我想我会发布一个新问题。

【问题讨论】:

    标签: cakephp file-upload phpunit cakephp-3.0


    【解决方案1】:

    您不一定需要修改验证规则,您可以选择使用实现\Psr\Http\Message\UploadedFileInterface 的对象。 CakePHP 的默认上传文件验证支持此类对象。

    CakePHP 需要zendframework/zend-diactoros,因此您可以使用\Zend\Diactoros\UploadedFile 并在您的测试中执行以下操作:

    $data = [
        // ...
        'file' => new \Zend\Diactoros\UploadedFile([
            '/path/to/the/temporary/file/on/disk',
            1234, // filesize in bytes
            \UPLOAD_ERR_OK, // upload (error) status
            'filename.jpg', // upload filename
            'image/jpeg' // upload mime type
        ])
    ];
    

    uploadedFile 规则会自动将此类对象视为上传的文件。

    当然,您处理文件上传的代码也必须支持该接口,但这并不复杂,您只需确保将常规文件上传数组转换为UploadedFileInterface 实现,以便您的上传处理程序可以把它作为一个要求。

    它当然可以在上传处理程序本身中完成,因此验证将使用常规文件上传数组以及UploadedFile 对象。另一种方法是在创建实体时更早地转换它们,使用 beforeMarshal 处理程序/事件,类似于以下内容:

    public function beforeMarshal(\Cake\Event\Event $event, \ArrayObject $data, \ArrayObject $options)
    {
        $file = \Cake\Utility\Hash::get($data, 'file');
        if ($file === null) {
            return;
        }
    
        if (!($file instanceof \Psr\Http\Message\UploadedFileInterface)) {
            if (!is_uploaded_file(\Cake\Utility\Hash::get($file, 'tmp_name'))) {
                $file = new \Zend\Diactoros\UploadedFile(
                    null,
                    0,
                    UPLOAD_ERR_NO_FILE,
                    null,
                    null
                );
            } else {
                $file = new \Zend\Diactoros\UploadedFile(
                    \Cake\Utility\Hash::get($file, 'tmp_name'),
                    \Cake\Utility\Hash::get($file, 'size'),
                    \Cake\Utility\Hash::get($file, 'error'),
                    \Cake\Utility\Hash::get($file, 'name'),
                    \Cake\Utility\Hash::get($file, 'type')
                );
            }
            $data['file'] = $file;
        }
    }
    

    这会将数据转换为UploadedFile 对象,以防它是实际上传的文件。添加此额外检查是因为 CakePHP 将文件数据与 POST 数据合并的行为,使得无法确定(除非可以访问请求对象或 $_FILES 超全局)来确定用户是否发布了该数据,或者 PHP 是否生成了该数据实际文件上传的数据。

    如果您随后使用\Psr\Http\Message\UploadedFileInterface::moveTo() 移动文件,它将在 SAPI(基于浏览器)以及非 SAPI (CLI) 环境中工作:

    try {
        $file->moveTo($targetPath);
    } catch (\Exception $exception) {
        $entity->setError(
            'file', [__('The file could not be moved to its destination.')]
        );
    }
    

    另见

    【讨论】:

    • 不错。 UploadedFileInterfact 是否仍然在正常的非 CLI 操作中执行 is_uploaded 检查?
    • @Roberto The specification 声明 moveTo()move_uploaded_file() 的替代品,并声明实现应该在 SAPI 环境中使用 is_uploaded_file()move_uploaded_file() 以确保有效性.虽然这允许实现使用其他方式来确保有效性,但 PHP 还没有提供任何其他本地方式,因此实现者实际上没有选择。 Zend 实现在非 CLI 环境中使用move_uploaded_file(),因此只会移动有效上传的文件。
    【解决方案2】:

    实际上,我在发布后几乎立即就想通了。
    解决方案基于https://pierrerambaud.com/blog/php/2012-12-29-testing-upload-file-with-php

    因此,解决此问题的唯一方法是覆盖两个内置函数:is_uploaded_filemove_uploaded_file

    uploadedFile 验证规则位于 Cake\Validation 内部,我在表格事件中使用 move 函数,所以在 App\Model\Table 内部。

    我在控制器测试用例的顶部添加了以下内容:

    <?php
    
    namespace Cake\Validation;
    
    function is_uploaded_file($filename)
    {
        return true;
    }
    
    namespace App\Model\Table;
    
    function move_uploaded_file($filename, $destination)
    {
        return copy($filename, $destination);
    }
    
    namespace App\Test\TestCase\Controller;
    
    use App\Controller\CarsController;
    use Cake\TestSuite\IntegrationTestTrait;
    use Cake\TestSuite\TestCase;
    use Cake\Core\Configure;
    
    /**
     * App\Controller\CarsController Test Case
     */
    class CarsControllerTest extends BaseTestCase
    {
    
        use IntegrationTestTrait;
    
        // ...
    

    而且它有效!

    【讨论】:

      猜你喜欢
      • 2017-10-19
      • 2017-08-30
      • 2017-10-07
      • 2017-09-14
      • 2019-10-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-14
      相关资源
      最近更新 更多