코드이그나이터4 뷰 다루기 - 12 - 첨부파일

koeunyeon·2021년 3월 24일
0

첨부파일

코드이그나이터4에서 첨부파일을 다루는 방법을 확인해 보겠습니다.
우리가 만들 페이지는 아래와 같습니다.

코드는 https://github.com/koeunyeon/ci4/tree/view-attach 에 있습니다.


View 컨트롤러에 아래의 메소드를 추가합니다.
app/Controllers/View.php

public function upload(): string
{
    $file = $this->request->getFile("single_file"); // (1)

    $fileInfo = []; // (2)
    if ($file != null) { // (3)
        if (!$file->isValid()) { // (4)
            $errorString = $file->getErrorString(); // (5)
            $errorCode = $file->getError(); // (6)

            $fileInfo['hasError'] = true; // (7)
            $fileInfo['errorString'] = $errorString;
            $fileInfo['errorCode'] = $errorCode;

        } else {  // (8)
            $fileInfo['hasError'] = false;
            if ($file->hasMoved() === false) {  // (9)                
                $fileInfo['mimeType'] = $file->getMimeType();  // (10)

                $savedPath = $file->store(); // (11)

                $fileInfo['savedPath'] = $savedPath;
                $fileInfo['clientName'] = $file->getClientName(); // (12)
                $fileInfo['name'] = $file->getName(); // (13)
                $fileInfo['clientMimeType'] = $file->getClientMimeType(); // (14)
                $fileInfo['clientExtention'] = $file->getClientExtension(); // (15)
                $fileInfo['guessExtention'] = $file->guessExtension(); // (16)
            }
        }
    }

    return View("/view/upload", [
        'fileInfo' => $fileInfo
    ]);
}

(1) 코드이그나이터4 컨트롤러에서 파일 정보를 가지고 오기 위해서는 $this->request->getFile(키이름) 구문을 사용합니다.

(2) 파일 정보를 뷰에 전달하기 위한 연관배열 $fileInfo 변수를 선언합니다. 이 변수에는 '키'로 파일 정보 구분이, '값'으로 정보가 들어갑니다.

(3) 파일이 첨부되지 않았으면 $this->request->getFile("single_file")null을 반환합니다. 따라서 $filenull인지 확인하면 파일이 첨부되었는지 알 수 있습니다.

(4) 파일이 성공적으로 업로드되었는지 확인하려면 $file->isValid() 메소드를 사용합니다. 성공적으로 파일이 업로드되지 않았다면 오류 메세지를 설정하기 위해 ! $file->isValid() 를 사용했습니다.

(5) 오류가 있다면 오류 메세지를 $file->getErrorString()으로 가져올 수 있습니다. 오류가 여러 개 있더라도 첫번째 오류에서 멈추므로 getErrorString은 값 하나만을 리턴합니다.
오류 코드는 아래와 같습니다. 기존 PHP에 익숙하다면 오류 코드가 동일하므로 PHP.NET 파일 업로드 오류 메세지 - https://www.php.net/manual/en/features.file-upload.errors.php를 참고하셔도 됩니다.

상수번호 | 상수이름 | 의미
--- | --- | ---
0 | UPLOAD_ERR_OK | 업로드가 성공했습니다.
1 | UPLOAD_ERR_INI_SIZE | 파일이 php.ini에 정의된 upload_max_filesize보다 용량이 큽니다. 참고로 XAMPP에 내장된 용량 제한은 40MB입니다.
2 | UPLOAD_ERR_FORM_SIZE | 파일이 폼에 정의된 업로드 한도를 초과합니다. PHP는 HTML FORM에 MAX_FILE_SIZE 항목이 있으면 이 값을 "최대 크기"라고 가정합니다. 단위는 byte여서 1MB 제한을 하려면 **(1024 * 1024 = 1,048,576)**을 입력하면 됩니다. UPLOAD_ERR_FORM_SIZE 오류는 MAX_FILE_SIZE 폼 값보다 실제 파일 크기가 더 클 경우 나옵니다.
3 | UPLOAD_ERR_PARTIAL | 파일이 부분적으로 업로드되었습니다. 즉 정상적으로 전부 파일이 업로드되지 않았습니다.
4 | UPLOAD_ERR_NO_FILE | 파일이 업로드되지 않았습니다. 파일이 선택되었으나 업로드되지 않은 경우에 나옵니다.
6 | UPLOAD_ERR_NO_TMP_DIR | 임시 디렉토리가 없어서 파일을 업로드할 수 없습니다. PHP는 파일을 업로드할 때 임시 디렉토리에 먼저 저장 후 move_uploaded_file 함수로 실제 저장할 디렉토리로 옮깁니다. 이 때 임시 디렉토리가 없으면 UPLOAD_ERR_NO_TMP_DIR 오류가 보여집니다. 참고로 업로드 임시 디렉토리는 php.iniupload_tmp_dir 항목에 설정되어 있습니다.
7 | UPLOAD_ERR_CANT_WRITE | 임시 디렉토리가 쓰기 금지되어 있습니다. 리눅스라면 쓰기 디렉토리 권한을 확인해 보세요. ls -l 임시디렉토리로 확인하고 chmod 755로 변경하면 됩니다. 윈도우즈라면 폴더 속성에서 쓰기 금지가 되어 있는지 확인할 수 있습니다.
8 | UPLOAD_ERR_EXTENSION | PHP는 보안 문제로 .php 확장자는 업로드가 불가능합니다. .php 파일이 업로드된 경우 오류를 내보냅니다.

(6) 오류 메세지 대신 **(5)**의 오류 코드를 내 보냅니다. 코드이그나이터4는 언어별로 오류 메세지를 설정할 수 있기 때문에 일반적으로 사용되지는 않지만, 추가적인 작업이 필요할 경우 정수를 리턴받을 수 있습니다.

(7) 오류가 있을 경우 hasError 플래그를 넣습니다.

(8) 파일이 정상적으로 업로드되었을 경우 처리를 시작합니다.

(9) 파일이 임시 디렉토리에서 실제 저장 디렉토리로 이동했는지 알아보려면 ->hasMoved()를 사용합니다.

(10) mimeType유추해서 알아냅니다.
사용자가 첨부한 파일은 확장자와 내용이 다를 수도 있기 때문에 실제 타입이 뭔지 점검해보는 것입니다. 예를 들어 확장자가 .jpg이지만 실제 내용이 .sh여서 쉘스크립트가 실행되면 곤란하기 때문에 이를 점검하기 위한 용도로 사용합니다.
주의할 점은 getMimeType메소드는 반드시 파일이 임시디렉토리에 있을 때만 사용 가능합니다. 만약 (11)의 move 혹은 store 메소드가 실행되고 나면 임시 디렉토리에서 파일이 사라지므로 getMimeType()메소드가 작동하지 않습니다.

(11) 파일을 임시 디렉토리에서 저장 디렉토리로 이동합니다.
코드이그나이터4에서 파일을 저장 디렉토리로 옮기는 방법은 move()store() 두 개가 있습니다. 둘 사이의 차이점은 move()는 원본 파일명 그대로 파일을 옮기거나, 직접 새 파일 이름을 만들어서 옮기는 메소드인데 반해 store는 날짜 디렉토리 YYYYMMDD 가 생성되고 임의의 이름으로 파일 이름을 변경합니다. 대부분의 경우 사용자가 입력한 파일명과 실제 저장된 파일명은 다른 게 좋으므로 특별한 사정이 없다면 move대신 store를 사용하세요.
store() 메소드는 YYYYMMDD로 시작하는 저장 디렉토리의 상대 경로를 반환합니다. 실제 파일은 기본 설정일 때 writable/uploads/ 아래에 저장됩니다.

(12) 사용자가 입력한 파일 이름을 가져옵니다. 파일 첨부의 경우 사용자가 입력한 파일 이름을 보여줘야 하는 경우가 많으므로 데이터베이스에 원본 이름을 저장하는 용도로 사용합니다.

(13) getName메소드는 저장된 이름을 반환합니다. (11)에서 store()를 사용했다면 YYYYMMDD를 제외하고 파일 이름만 반환합니다.

(14) 웹 브라우저가 보낸 mimeType을 읽습니다. (10)에서 언급했듯이 웹브라우저의 입력은 "믿으면 안됩"니다. 그냥 참고용으로 사용하세요.

(15) 웹브라우저가 보낸 확장자를 읽습니다. (14)와 마찬가지로 사용자의 입력은 믿지 마세요. 대신 확장자로 뭔가를 해야 한다면 (16)을 이용해 확장자를 유추하는 게 좋습니다.

(16) 실제 확장자를 "유추"합니다. 만약 .png 파일이 입력되었어도 내용이 .txt라면 .guessExtention.txt를 리턴합니다. 특정 타입의 파일만 입력되어야 하는 경우 guessExtension 메소드로 확인하면 됩니다.


뷰를 추가하겠습니다.
app/Views/view/upload.php

<form method="POST" enctype="multipart/form-data"> <!-- (1) --><!-- (2) -->
    <p>
        단일 파일 업로드
        <input type="file" name="single_file" /> <!-- (3) -->
    </p>
    <input type="submit" value="입력" />
    <hr />
    <?php foreach ($fileInfo as $key => $val) : ?> <!-- (4) -->
        <p><?= $key ?> : <?= $val ?></p>
    <?php endforeach; ?>
</form>

(1) 첨부 파일은 반드시 POST 메소드로 전달되어야 합니다. 웹 브라우저는 첨부 파일을 인코딩해서 바이너리로 만든 다음 HTTP 본문에 실어서 서버로 전달하기 때문입니다.

(2) enctype="multipart/form-data"은 서버로 데이터를 전달할 때 인코딩하는 방법을 나타냅니다.
enctype은 총 3가지 종류가 있습니다.

  • application/x-www-form-urlencoded : 폼 데이터를 "텍스트" 인코딩합니다. POST 요청의 기본값입니다. 예를 들어 Hello 안녕하세요application/x-www-form-urlencoded 타입으로 인코딩되면 Hello+%EC%95%88%EB%85%95%ED%95%98%EC%84%B8%EC%9A%94로 변환됩니다.
  • multipart/form-data : 모든 데이터를 "바이너리" 인코딩합니다. 바이너리 인코딩을 하면 업로드하는 내용과 메타 데이터를 함께 전송할 수 있고, 본문도 압축해서 전송할 수 있기 때문에 페이로드 용량이 작아지는 장점이 생깁니다. 반면 단순한 데이터의 경우 메타데이터의 용량만큼 더 많은 데이터가 전송되어야 하고, 데이터를 인코딩/디코딩하는 과정에서 더 많은 시스템 리소스를 사용하게 되므로 일반적으로 파일을 업로드할 때만 사용됩니다.
  • text/plain : 공백만 +로 치환하고 나머지는 치환하지 않습니다. 단순 텍스트 전달에 사용되는 포멧이지만 현실적으로 POST 요청에는 거의 사용되지 않습니다. GET 메소드의 파라미터는 기본으로 text/plain으로 전달됩니다.

(3) 파일을 업로드하는 HTML 태그는 <input type="file" 입니다. 이름은 컨트롤러에서 설정한 single_file입니다.

(4) 업로드한 파일 결과를 보여줍니다.


브라우저에서 http://localhost:8080/view/upload에 접속해 결과를 확인합니다.

profile
스타트업에 관심이 많은 10 + n년차 웹 개발자. 자바 스프링 (혹은 부트), 파이썬 플라스크, PHP를 주로 다룹니다.

0개의 댓글