업로드된 파일 다루기

CodeIgniter는 PHP의 $_FILES 배열을 직접 사용하는 것보다 폼을 통해 업로드된 파일을 훨씬 간단하고 안전하게 다룰 수 있게 해줍니다. 이는 File 클래스를 확장하므로 해당 클래스의 모든 기능을 사용할 수 있습니다.

참고

이것은 CodeIgniter 3의 파일 업로드 클래스와 다릅니다. 업로드된 파일에 대한 원시 인터페이스를 몇 가지 기능과 함께 제공합니다.

파일 업로드 폼 튜토리얼

파일 업로드는 다음과 같은 일반적인 과정을 포함합니다:

  • 사용자가 파일을 선택하고 업로드할 수 있는 업로드 폼이 표시됩니다.

  • 폼이 제출되면 지정한 목적지에 파일이 업로드됩니다.

  • 이 과정에서 설정한 기본 설정에 따라 파일이 업로드 허용 여부를 검증합니다.

  • 업로드가 완료되면 사용자에게 성공 메시지가 표시됩니다.

이 과정을 설명하기 위한 간략한 튜토리얼이 있습니다. 이후에 참조 정보를 확인할 수 있습니다.

업로드 폼 만들기

텍스트 편집기를 사용하여 upload_form.php라는 폼을 만듭니다. 그 안에 이 코드를 넣고 app/Views 디렉터리에 저장합니다:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Upload Form</title>
</head>
<body>

<?php foreach ($errors as $error): ?>
    <li><?= esc($error) ?></li>
<?php endforeach ?>

<?= form_open_multipart('upload/upload') ?>
    <input type="file" name="userfile" size="20">
    <br><br>
    <input type="submit" value="upload">
</form>

</body>
</html>

폼 헬퍼를 사용하여 폼 여는 태그를 생성하고 있음을 알 수 있습니다. 파일 업로드는 멀티파트 폼이 필요하므로 헬퍼가 적절한 구문을 생성해 줍니다.

$errors 변수도 있음을 알 수 있습니다. 사용자가 잘못된 작업을 한 경우 오류 메시지를 표시하기 위해 사용됩니다.

성공 페이지

텍스트 편집기를 사용하여 upload_success.php라는 폼을 만듭니다. 그 안에 이 코드를 넣고 app/Views 디렉터리에 저장합니다:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Upload Form</title>
</head>
<body>

<h3>Your file was successfully uploaded!</h3>

<ul>
    <li>name: <?= esc($uploaded_fileinfo->getBasename()) ?></li>
    <li>size: <?= esc($uploaded_fileinfo->getSizeByUnit('kb')) ?> KB</li>
    <li>extension: <?= esc($uploaded_fileinfo->guessExtension()) ?></li>
</ul>

<p><?= anchor('upload', 'Upload Another File!') ?></p>

</body>
</html>

컨트롤러

텍스트 편집기를 사용하여 Upload.php라는 컨트롤러를 만듭니다. 그 안에 이 코드를 넣고 app/Controllers 디렉터리에 저장합니다:

<?php

namespace App\Controllers;

use CodeIgniter\Files\File;

class Upload extends BaseController
{
    protected $helpers = ['form'];

    public function index()
    {
        return view('upload_form', ['errors' => []]);
    }

    public function upload()
    {
        $validationRule = [
            'userfile' => [
                'label' => 'Image File',
                'rules' => [
                    'uploaded[userfile]',
                    'is_image[userfile]',
                    'mime_in[userfile,image/jpg,image/jpeg,image/gif,image/png,image/webp]',
                    'max_size[userfile,100]',
                    'max_dims[userfile,1024,768]',
                ],
            ],
        ];
        if (! $this->validateData([], $validationRule)) {
            $data = ['errors' => $this->validator->getErrors()];

            return view('upload_form', $data);
        }

        $img = $this->request->getFile('userfile');

        if (! $img->hasMoved()) {
            $filepath = WRITEPATH . 'uploads/' . $img->store();

            $data = ['uploaded_fileinfo' => new File($filepath)];

            return view('upload_success', $data);
        }

        $data = ['errors' => 'The file has already been moved.'];

        return view('upload_form', $data);
    }
}

업로드된 파일의 유효성 검사에는 파일 업로드 규칙만 사용할 수 있습니다.

따라서 required 규칙도 사용할 수 없으므로, 파일이 필수인 경우 대신 uploaded 규칙을 사용하십시오.

$this->validateData()의 첫 번째 인수로 빈 배열([])이 전달된다는 점에 주목하십시오. 파일 유효성 검사 규칙이 업로드된 파일의 데이터를 Request 객체에서 직접 가져오기 때문입니다.

폼에 파일 업로드 외의 다른 필드가 있다면 필드 데이터를 첫 번째 인수로 전달합니다.

라우트

텍스트 편집기를 사용하여 app/Config/Routes.php를 엽니다. 그 안에 다음 두 라우트를 추가합니다:

<?php

// ...

/*
 * --------------------------------------------------------------------
 * Route Definitions
 * --------------------------------------------------------------------
 */

// We get a performance increase by specifying the default
// route since we don't have to scan directories.
$routes->get('/', 'Home::index');

$routes->get('upload', 'Upload::index');          // Add this line.
$routes->post('upload/upload', 'Upload::upload'); // Add this line.

// ...

업로드 디렉터리

업로드된 파일은 writable/uploads/ 디렉터리에 저장됩니다.

사용해 보세요!

폼을 사용해 보려면 다음과 유사한 URL로 사이트를 방문합니다:

example.com/index.php/upload/

업로드 폼이 표시될 것입니다. 이미지 파일(jpg, gif, png, 또는 webp)을 업로드해 보세요. 컨트롤러의 경로가 올바르면 작동해야 합니다.

파일 접근

모든 파일

파일을 업로드하면 PHP에서 $_FILES 슈퍼전역 변수를 통해 기본적으로 접근할 수 있습니다. 이 배열은 여러 파일을 동시에 업로드할 때 몇 가지 주요 단점이 있으며, 많은 개발자가 인식하지 못하는 잠재적인 보안 취약점이 있습니다. CodeIgniter는 공통 인터페이스 뒤에서 파일 사용을 표준화하여 이러한 두 가지 상황을 모두 해결합니다.

파일은 현재 IncomingRequest 인스턴스를 통해 접근합니다. 이 요청과 함께 업로드된 모든 파일을 가져오려면 getFiles()를 사용합니다. CodeIgniter\HTTP\Files\UploadedFile의 인스턴스로 표현되는 파일 배열을 반환합니다:

<?php

$files = $this->request->getFiles();

물론 파일 입력의 이름을 지정하는 방법은 여러 가지가 있으며, 가장 단순한 방법이 아니면 예상치 못한 결과가 발생할 수 있습니다. 배열은 예상하는 방식으로 반환됩니다. 가장 단순한 사용법으로 단일 파일은 다음과 같이 제출될 수 있습니다:

<input type="file" name="avatar">

이는 다음과 같은 단순 배열을 반환합니다:

[
    'avatar' => // UploadedFile instance,
];

참고

UploadedFile 인스턴스는 $_FILES에 대응합니다. 사용자가 제출 버튼만 클릭하고 파일을 업로드하지 않더라도 인스턴스는 존재합니다. UploadedFile의 isValid() 메서드로 파일이 실제로 업로드되었는지 확인할 수 있습니다. 파일 검증을 참조하십시오.

이름에 배열 표기법을 사용한 경우 입력은 다음과 같이 표시됩니다:

<input type="file" name="my-form[details][avatar]">

getFiles()가 반환하는 배열은 다음과 같습니다:

[
     'my-form' => [
        'details' => [
            'avatar' => // UploadedFile instance
        ],
    ],
]

경우에 따라 업로드할 파일 배열을 지정할 수 있습니다:

Upload an avatar: <input type="file" name="my-form[details][avatars][]">
Upload an avatar: <input type="file" name="my-form[details][avatars][]">

이 경우 반환되는 파일 배열은 다음과 같습니다:

[
    'my-form' => [
        'details' => [
            'avatar' => [
                0 => // UploadedFile instance,
                1 => // UploadedFile instance,
            ],
        ],
    ],
]

단일 파일

단일 파일에만 접근하면 되는 경우 getFile()을 사용하여 파일 인스턴스를 직접 가져올 수 있습니다. CodeIgniter\HTTP\Files\UploadedFile의 인스턴스를 반환합니다:

가장 단순한 사용법

가장 단순한 사용법으로 단일 파일은 다음과 같이 제출될 수 있습니다:

<input type="file" name="userfile">

이는 다음과 같은 단순 파일 인스턴스를 반환합니다:

<?php

$file = $this->request->getFile('userfile');

배열 표기법

이름에 배열 표기법을 사용한 경우 입력은 다음과 같이 표시됩니다:

<input type="file" name="my-form[details][avatar]">

파일 인스턴스를 가져오려면:

<?php

$file = $this->request->getFile('my-form.details.avatar');

여러 파일

<input type="file" name="images[]" multiple>

컨트롤러에서:

<?php

if ($imagefile = $this->request->getFiles()) {
    foreach ($imagefile['images'] as $img) {
        if ($img->isValid() && ! $img->hasMoved()) {
            $newName = $img->getRandomName();
            $img->move(WRITEPATH . 'uploads', $newName);
        }
    }
}

여기서 images는 폼 필드 이름으로부터의 반복입니다.

같은 이름의 파일이 여러 개인 경우 getFile()을 사용하여 각 파일을 개별적으로 가져올 수 있습니다.

컨트롤러에서:

<?php

$file1 = $this->request->getFile('images.0');
$file2 = $this->request->getFile('images.1');

같은 이름의 업로드된 파일 배열을 가져오려면 getFileMultiple()을 사용하는 것이 더 편리할 수 있습니다:

<?php

$files = $this->request->getFileMultiple('images');

다른 예시:

Upload an avatar: <input type="file" name="my-form[details][avatars][]">
Upload an avatar: <input type="file" name="my-form[details][avatars][]">

컨트롤러에서:

<?php

$file1 = $this->request->getFile('my-form.details.avatars.0');
$file2 = $this->request->getFile('my-form.details.avatars.1');

참고

getFiles()를 사용하는 것이 더 적절합니다.

파일 다루기

UploadedFile 인스턴스를 가져온 후에는 파일에 대한 정보를 안전하게 얻을 수 있으며 파일을 새 위치로 이동할 수도 있습니다.

파일 검증

isValid() 메서드를 호출하여 파일이 오류 없이 HTTP를 통해 실제로 업로드되었는지 확인할 수 있습니다:

<?php

if (! $file->isValid()) {
    throw new \RuntimeException($file->getErrorString() . '(' . $file->getError() . ')');
}

이 예시에서 볼 수 있듯이, 파일에 업로드 오류가 있으면 getError()getErrorString() 메서드로 오류 코드(정수)와 오류 메시지를 가져올 수 있습니다. 이 메서드를 통해 다음 오류를 발견할 수 있습니다:

  • 파일이 upload_max_filesize ini 지시자를 초과합니다.

  • 파일이 폼에 정의된 업로드 제한을 초과합니다.

  • 파일이 일부만 업로드되었습니다.

  • 업로드된 파일이 없습니다.

  • 파일을 디스크에 쓸 수 없습니다.

  • 파일을 업로드할 수 없습니다: 임시 디렉터리가 없습니다.

  • PHP 확장에 의해 파일 업로드가 중단되었습니다.

파일 이름

getName()

getName() 메서드로 클라이언트가 제공한 원래 파일 이름을 가져올 수 있습니다. 이는 일반적으로 클라이언트가 보낸 파일 이름이므로 신뢰해서는 안 됩니다. 파일이 이동된 경우 이동된 파일의 최종 이름을 반환합니다:

<?php

$name = $file->getName();

getClientName()

파일이 이동된 경우에도 클라이언트가 보낸 업로드된 파일의 원래 이름을 항상 반환합니다:

<?php

$originalName = $file->getClientName();

getTempName()

업로드 중 생성된 임시 파일의 전체 경로를 가져오려면 getTempName() 메서드를 사용할 수 있습니다:

<?php

$tempfile = $file->getTempName();

기타 파일 정보

getClientExtension()

업로드된 파일 이름을 기반으로 원래 파일 확장자를 반환합니다:

<?php

$ext = $file->getClientExtension();

경고

이것은 신뢰할 수 없는 출처입니다. 신뢰할 수 있는 버전은 대신 guessExtension()을 사용하십시오.

getClientMimeType()

클라이언트가 제공한 파일의 MIME 타입을 반환합니다. 이것은 신뢰할 수 없는 값입니다. 신뢰할 수 있는 버전은 대신 getMimeType()을 사용하십시오:

<?php

$type = $file->getClientMimeType();

echo $type; // image/png

getClientPath()

Added in version 4.4.0.

클라이언트가 디렉터리 업로드를 통해 파일을 업로드한 경우 업로드된 파일의 webkit 상대 경로를 반환합니다. PHP 8.1 미만 버전에서는 null을 반환합니다.

<?php

$clientPath = $file->getClientPath();
echo $clientPath; // dir/file.txt, or dir/sub_dir/file.txt

파일 이동

원래 파일 이름으로

각 파일은 적절하게 이름 붙여진 move() 메서드를 사용하여 새 위치로 이동할 수 있습니다. 첫 번째 파라미터로 파일을 이동할 디렉터리를 받습니다:

<?php

$file->move(WRITEPATH . 'uploads');

기본적으로 원래 파일 이름이 사용됩니다.

새 파일 이름으로

두 번째 파라미터로 전달하여 새 파일 이름을 지정할 수 있습니다:

<?php

$newName = $file->getRandomName();
$file->move(WRITEPATH . 'uploads', $newName);

기존 파일 덮어쓰기

기본적으로 대상 파일이 이미 존재하면 새 파일 이름이 사용됩니다. 예를 들어, 디렉터리에 image_name.jpg가 이미 존재하면 파일 이름은 자동으로 image_name_1.jpg가 됩니다.

세 번째 파라미터로 true를 전달하여 기존 파일을 덮어쓸 수 있습니다:

<?php

$file->move(WRITEPATH . 'uploads', null, true);

파일 이동 여부 확인

파일이 이동되면 임시 파일이 삭제됩니다. hasMoved() 메서드로 파일이 이미 이동되었는지 확인할 수 있으며, 불리언을 반환합니다:

<?php

if ($file->isValid() && ! $file->hasMoved()) {
    $file->move($path);
}

이동 실패 시

업로드된 파일 이동은 다음과 같은 여러 상황에서 HTTPException과 함께 실패할 수 있습니다:

  • 파일이 이미 이동된 경우

  • 파일이 성공적으로 업로드되지 않은 경우

  • 파일 이동 작업이 실패한 경우 (예: 권한 부족)

파일 저장

각 파일은 적절하게 이름 붙여진 store() 메서드를 사용하여 새 위치로 이동할 수 있습니다.

가장 단순한 사용법으로 단일 파일은 다음과 같이 제출될 수 있습니다:

<input type="file" name="userfile">

기본적으로 업로드 파일은 writable/uploads 디렉터리에 저장됩니다. YYYYMMDD 폴더와 무작위 파일 이름이 생성됩니다. 파일 경로를 반환합니다:

<?php

$path = $this->request->getFile('userfile')->store();

첫 번째 파라미터로 파일을 이동할 디렉터리를 지정할 수 있습니다. 두 번째 파라미터로 전달하여 새 파일 이름을 지정합니다:

<?php

$path = $this->request->getFile('userfile')->store('head_img/', 'user_name.jpg');

업로드된 파일 이동은 다음과 같은 여러 상황에서 HTTPException과 함께 실패할 수 있습니다:

  • 파일이 이미 이동된 경우

  • 파일이 성공적으로 업로드되지 않은 경우

  • 파일 이동 작업이 실패한 경우 (예: 권한 부족)