php를 이용한 파일업로드 기능

Hyeseong·2023년 5월 25일
0

들어가기 전

php를 이용한 프로젝트에 투입되었으며 php의 OOP에 대한 적응을 위한 간단한 기능 구현을 하려함.

구현 사항

  • OOP의 다형성을 이용하여 파일 업로드 기능 구현 코드 작성

코드

  • 전체 코드
<?php
interface FileUploaderInterface {
  public function handleFileUpload(): string;
  public function isFileNotSelected(): bool;
  public function isValidExtension(string $extension): bool;
  public function isValidSize(int $size): bool;
  public function moveUploadedFile(string $tmp_name, string $target_dir): bool;
}

interface MessageInterface {
  public function showMessage(string $message, string $color): string;
}

class BaseMessage implements MessageInterface {
  protected function wrapMessage(string $message, string $color): string {
    return '<p style="color: ' . $color . ';">' . $message . '</p>';
  }

  public function showMessage(string $message, string $color): string {
    return $this->wrapMessage($message, $color);
  }
}

class ErrorMessage extends BaseMessage {
  // No need to override showMessage() method as it is inherited from BaseMessage
}

class SuccessMessage extends BaseMessage {
  // No need to override showMessage() method as it is inherited from BaseMessage
}

class CustomFileUploader implements FileUploaderInterface {
  protected $allowedExtensions;
  protected $uploadDirectory;
  protected $errorMessage;
  protected $successMessage;

  public function __construct(array $allowedExtensions, string $uploadDirectory, MessageInterface $errorMessage, MessageInterface $successMessage) {
    $this->allowedExtensions = $allowedExtensions;
    $this->uploadDirectory = $uploadDirectory;
    $this->errorMessage = $errorMessage;
    $this->successMessage = $successMessage;
  }

  public function handleFileUpload(): string {
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
      return '';
    }

    if ($this->isFileNotSelected()) {
      return $this->errorMessage->showMessage('Please choose a file!', 'red');
    }

    $file = $_FILES['upload'];
    $file_name = $file['name'];
    $file_size = $file['size'];
    $file_tmp_name = $file['tmp_name'];

    $file_ext = pathinfo($file_name, PATHINFO_EXTENSION);
    $file_ext = strtolower($file_ext);

    if (!$this->isValidExtension($file_ext)) {
      return $this->errorMessage->showMessage('Invalid file type!', 'red');
    }

    if (!$this->isValidSize($file_size)) {
      return $this->errorMessage->showMessage('File too large!', 'red');
    }

    $target_dir = $this->uploadDirectory . '/' . $file_name;

    if (!is_dir($this->uploadDirectory)) {
      mkdir($this->uploadDirectory, 0755, true);
    }

    if ($this->moveUploadedFile($file_tmp_name, $target_dir)) {
      return $this->successMessage->showMessage('File uploaded!', 'green');
    } else {
      return $this->errorMessage->showMessage('Failed to move the uploaded file!', 'red');
    }
  }

  public function isFileNotSelected(): bool {
    return empty($_FILES['upload']['name']);
  }

  public function isValidExtension(string $extension): bool {
    return in_array($extension, $this->allowedExtensions);
  }

  public function isValidSize(int $size): bool {
    $maxSize = 1000000;
    return $size <= $maxSize;
  }

  public function moveUploadedFile(string $tmp_name, string $target_dir): bool {
    return move_uploaded_file($tmp_name, $target_dir);
  }
}

$allowedExtensions = ['png', 'jpg', 'jpeg', 'gif'];
$uploadDirectory = 'uploads';
$errorMessage = new ErrorMessage();
$successMessage = new SuccessMessage();
$fileUploader = new CustomFileUploader($allowedExtensions, $uploadDirectory, $errorMessage, $successMessage);
$message = $fileUploader->handleFileUpload();

?>

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>File Upload</title>
</head>

<body>
  <form action="<?php echo htmlspecialchars($_SERVER['PHP_SELF']); ?>" method="post" enctype="multipart/form-data">
    Select image to upload:
    <input type="file" name='upload'>
    <input type="submit" value="Submit" name='submit'>
  </form>

  <?php if (isset($message)) echo $message; ?>
</body>

</html>

설명

FileUploaderInterface

<?php

interface FileUploaderInterface {
  public function handleFileUpload(): string;
  public function isFileNotSelected(): bool;
  public function isValidExtension(string $extension): bool;
  public function isValidSize(int $size): bool;
  public function moveUploadedFile(string $tmp_name, string $target_dir): bool;
}
  • 위의 코드는 FileUploaderInterface라는 인터페이스를 정의하는 부분입니다. 이 인터페이스는 파일 업로드와 관련된 동작을 수행하기 위한 메서드를 선언합니다.
  1. handleFileUpload(): string: 파일 업로드를 처리하는 메서드입니다. 이 메서드는 파일 업로드 과정을 수행하고, 결과에 대한 문자열을 반환합니다.

  2. isFileNotSelected(): bool: 파일이 선택되지 않았는지 확인하는 메서드입니다. 파일이 선택되지 않은 경우 true를 반환하고, 선택된 경우 false를 반환합니다.

  3. isValidExtension(string $extension): bool: 주어진 확장자가 유효한지 확인하는 메서드입니다. 매개변수로 확장자를 받아 해당 확장자가 유효한 경우 true를 반환하고, 그렇지 않은 경우 false를 반환합니다.

  4. isValidSize(int $size): bool: 주어진 파일 크기가 유효한지 확인하는 메서드입니다. 매개변수로 파일 크기를 받아 해당 크기가 유효한 경우 true를 반환하고, 그렇지 않은 경우 false를 반환합니다.

  5. moveUploadedFile(string $tmp_name, string $target_dir): bool: 업로드된 파일을 지정된 디렉토리로 이동하는 메서드입니다. 매개변수로 임시 파일 경로(tmpname)와이동할대상디렉토리경로(tmp_name)와 이동할 대상 디렉토리 경로(target_dir)를 받아 파일을 이동하고, 이동이 성공한 경우 true를 반환하고, 실패한 경우 false를 반환합니다.

  6. 결론 : 이 인터페이스는 파일 업로드와 관련된 클래스에서 구현될 때, 각 메서드가 정확히 어떤 동작을 수행해야 하는지를 명시하고, 타입 힌트를 통해 메서드의 매개변수와 반환 타입을 명확히 지정할 수 있도록 도와줍니다.

톺아보기-1

  1. php에서 interface클래스가 구현해야 하는 메서드의 목록을 정의하는 역할
  2. 메서드 시그니처(이름, 매개변수, 반환값의 형식)만 포함
  3. 구현내용은 갖지 않음.
  4. 특징
    4.1. interface키워드를 사용하여 정의
    4.2. 인터페이스는 class 키워드를 사용하여 구현되는 클래스에 의해 구현됩니다.
    4.3. 인터페이스에서 선언된 모든 메서드는 공개(public)로 간주됩니다. 즉, 메서드는 클래스에서 구현될 때 반드시 공개적으로 선언되어야 합니다.
    4.4. 클래스는 하나 이상의 인터페이스를 구현할 수 있으며, 인터페이스는 다중 상속과 유사한 효과를 제공합니다.
    4.5. 인터페이스는 객체 간의 관계를 정의하고 클래스 간의 결합도를 낮출 수 있습니다.
  5. 예시
interface Animal {
  public function makeSound();
}

class Dog implements Animal {
  public function makeSound() {
    echo "Woof!";
  }
}

class Cat implements Animal {
  public function makeSound() {
    echo "Meow!";
  }
}

$dog = new Dog();
$dog->makeSound(); // 출력: Woof!

$cat = new Cat();
$cat->makeSound(); // 출력: Meow!

MessageInterface

interface MessageInterface {
  public function showMessage(string $message, string $color): string;
}

class BaseMessage implements MessageInterface {
  protected function wrapMessage(string $message, string $color): string {
    return '<p style="color: ' . $color . ';">' . $message . '</p>';
  }

  public function showMessage(string $message, string $color): string {
    return $this->wrapMessage($message, $color);
  }
}

class ErrorMessage extends BaseMessage {
  // No need to override showMessage() method as it is inherited from BaseMessage
}

class SuccessMessage extends BaseMessage {
  // No need to override showMessage() method as it is inherited from BaseMessage
}

설명

  • 위의 코드는 메시지를 표시하는 기능을 구현하기 위한 인터페이스와 클래스들을 포함하고 있습니다.
  1. MessageInterfaceshowMessage라는 메서드를 가지는 인터페이스입니다. 이 인터페이스는 메시지와 컬러를 매개변수로 받아서 문자열로 변환하여 반환하는 기능을 정의합니다.

  2. BaseMessageMessageInterface구현한 클래스입니다. BaseMessage는 wrapMessage라는 보호된 메서드를 가지고 있습니다. 이 메서드는 메시지와 컬러를 매개변수로 받아서 특정 형식으로 래핑된 문자열을 반환합니다. BaseMessage는 또한 MessageInterface의 showMessage 메서드를 구현하며, 내부적으로 wrapMessage 메서드를 호출하여 변환된 문자열을 반환합니다.

  3. ErrorMessage는 BaseMessage를 상속한 클래스입니다. ErrorMessage는 BaseMessage에서 상속받은 showMessage 메서드를 그대로 사용하며, 추가적인 메서드를 구현하지 않습니다.

  4. SuccessMessage도 BaseMessage를 상속한 클래스로, BaseMessage에서 상속받은 showMessage 메서드를 그대로 사용합니다.

이렇게 구현된 클래스들을 사용하면 메시지와 컬러를 전달하여 특정 형식으로 래핑된 메시지를 얻을 수 있습니다.

톺아보기-2

PHP에서는 protected, public, private 외에도 finalstatic을 포함한 몇 가지 가시성 및 특성 키워드를 제공합니다.

  1. protected: protected 키워드는 해당 멤버(프로퍼티 또는 메서드)가 정의된 클래스 내부 및 해당 클래스를 상속받은 자식 클래스 내부에서 접근할 수 있음을 나타냅니다.

  2. public: public 키워드는 해당 멤버가 어디에서나 접근할 수 있음을 나타냅니다. 즉, 클래스 내부, 자식 클래스, 클래스 인스턴스어디서든 접근 가능합니다.

  3. private: private 키워드는 해당 멤버가 정의된 클래스 내부에서만 접근할 수 있음을 나타냅니다. 자식 클래스나 클래스 인스턴스에서는 접근할 수 없습니다.

  4. final: final 키워드는 클래스를 확장(상속)할 수 없음을 나타냅니다. final로 선언된 클래스는 더 이상 상속할 수 없으며, 메서드를 final로 선언하면 해당 메서드를 오버라이딩할 수 없습니다.

  5. static: static 키워드는 클래스 레벨에 속하는 멤버(프로퍼티 또는 메서드)를 나타냅니다. static 멤버는 클래스 인스턴스를 생성하지 않고도 호출할 수 있으며, 인스턴스 간에 공유됩니다.

CustomFileUploader

class CustomFileUploader implements FileUploaderInterface {
  protected $allowedExtensions;
  protected $uploadDirectory;
  protected $errorMessage;
  protected $successMessage;

  public function __construct(array $allowedExtensions, string $uploadDirectory, MessageInterface $errorMessage, MessageInterface $successMessage) {
    $this->allowedExtensions = $allowedExtensions;
    $this->uploadDirectory = $uploadDirectory;
    $this->errorMessage = $errorMessage;
    $this->successMessage = $successMessage;
  }
  • 위의 코드는 CustomFileUploader 클래스의 생성자에 두 개의 인터페이스 형식의 매개변수 errorMessage와 successMessage가 추가되었습니다. 이러한 매개변수를 통해 ErrorMessage 및 SuccessMessage와 같은 메시지 클래스의 인스턴스를 주입할 수 있습니다.

  • 기존에는 CustomFileUploader 클래스 내에서 ErrorMessage와 SuccessMessage를 직접 생성했지만, 이제는 외부에서 이러한 메시지 클래스의 인스턴스를 생성한 후에 CustomFileUploader의 생성자를 통해 주입합니다. 이는 의존성 주입(Dependency Injection) 패턴을 사용하여 클래스 간의 결합도를 낮추고 유연성을 높이는 방식입니다.

  • 이렇게 함으로써, CustomFileUploader 클래스는 어떤 종류의 메시지 클래스도 사용할 수 있게 되며, 새로운 메시지 클래스를 만들어서 주입하는 것도 가능합니다. 이는 코드의 확장성과 유지보수성을 향상시키는 데 도움이 됩니다.

  • 생성자의 시그니처를 변경함으로써, 이제 CustomFileUploader 클래스를 인스턴스화할 때 메시지 클래스의 인스턴스를 제공해야 합니다. 예를 들어:

$allowedExtensions = ['png', 'jpg', 'jpeg', 'gif'];
$uploadDirectory = 'uploads';
$errorMessage = new ErrorMessage();
$successMessage = new SuccessMessage();

$fileUploader = new CustomFileUploader($allowedExtensions, $uploadDirectory, $errorMessage, $successMessage);
  • 위와 같이 ErrorMessage와 SuccessMessage 클래스의 인스턴스를 생성한 후, 이를 CustomFileUploader 클래스의 생성자에 주입합니다. 이제 CustomFileUploader 내에서는 주입된 메시지 클래스의 인스턴스를 사용하여 에러 메시지 및 성공 메시지를 생성할 수 있습니다.

handleFileUpload

public function handleFileUpload(): string {
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
      return '';
    }

    if ($this->isFileNotSelected()) {
      return $this->errorMessage->showMessage('Please choose a file!', 'red');
    }

    $file = $_FILES['upload'];
    $file_name = $file['name'];
    $file_size = $file['size'];
    $file_tmp_name = $file['tmp_name'];

    $file_ext = pathinfo($file_name, PATHINFO_EXTENSION);
    $file_ext = strtolower($file_ext);

    if (!$this->isValidExtension($file_ext)) {
      return $this->errorMessage->showMessage('Invalid file type!', 'red');
    }

    if (!$this->isValidSize($file_size)) {
      return $this->errorMessage->showMessage('File too large!', 'red');
    }

    $target_dir = $this->uploadDirectory . '/' . $file_name;

    if (!is_dir($this->uploadDirectory)) {
      mkdir($this->uploadDirectory, 0755, true);
    }

    if ($this->moveUploadedFile($file_tmp_name, $target_dir)) {
      return $this->successMessage->showMessage('File uploaded!', 'green');
    } else {
      return $this->errorMessage->showMessage('Failed to move the uploaded file!', 'red');
    }
  }
  • 위의 코드는 handleFileUpload() 메서드의 내용입니다. 이 메서드는 파일 업로드를 처리하고 결과에 따라 적절한 메시지를 반환합니다.

코드의 실행 흐름은 다음과 같습니다:

  1. $_SERVER['REQUEST_METHOD'] 값을 확인하여 현재 요청이 POST 메서드인지 확인합니다. 만약 POST 메서드가 아니라면 빈 문자열을 반환하고 메서드 실행을 종료합니다.

  2. 파일이 선택되지 않았는지 확인하기 위해 $this->isFileNotSelected() 메서드를 호출합니다. 파일이 선택되지 않았다면 'Please choose a file!' 메시지와 'red' 색상을 사용하여 $this->errorMessage 객체showMessage() 메서드호출하고 해당 결과를 반환합니다.

  3. 선택된 파일이 있다면, 파일의 정보를 변수에 할당합니다. 파일 이름은 $file_name, 파일 크기는 $file_size, 임시 파일 경로는 $file_tmp_name에 저장됩니다.

  4. 파일 이름으로부터 확장자를 추출하기 위해 pathinfo() 함수를 사용하고, 추출된 확장자를 소문자로 변환합니다.

  5. 추출된 확장자가 유효한지 확인하기 위해 $this->isValidExtension() 메서드를 호출합니다. 유효하지 않은 경우 'Invalid file type!' 메시지와 'red' 색상을 사용하여 $this->errorMessage 객체의 showMessage() 메서드를 호출하고 해당 결과를 반환합니다.

  6. 파일 크기가 유효한지 확인하기 위해 $this->isValidSize() 메서드를 호출합니다. 유효하지 않은 경우 'File too large!' 메시지와 'red' 색상을 사용하여 $this->errorMessage 객체showMessage() 메서드를 호출하고 해당 결과를 반환합니다.

  7. 업로드된 파일을 저장하기 위한 대상 디렉토리 경로인 $target_dir을 생성합니다. 대상 디렉토리가 존재하지 않는 경우에는 mkdir() 함수를 사용하여 디렉토리를 생성합니다.

  8. $this->moveUploadedFile() 메서드를 호출하여 업로드된 파일을 이동시킵니다. 이동에 성공한 경우 'File uploaded!' 메시지와 'green' 색상을 사용하여 $this->successMessage 객체showMessage() 메서드를 호출하고 해당 결과를 반환합니다.

  9. 이동에 실패한 경우 'Failed to move the uploaded file!' 메시지와 'red' 색상을 사용하여 $this->errorMessage 객체showMessage() 메서드를 호출하고 해당 결과를 반환합니다.

  10. 결론 : 이렇게하여 handleFileUpload() 메서드는 파일 업로드 처리 후 결과에 따라 적절한 메시지를 반환합니다. 이 메서드는 errorMessage와 successMessage 객체를 사용하여 에러 메시지성공 메시지를 생성하며, 이전에 주입된 객체들과 협력하여 동작합니다.

톺아보기-3

php의 built-in으로 제공되는 복합 데이터 타입은?

  1. 배열 (Array): 여러 개의 값을 하나의 변수에 저장하는 데이터 구조입니다. 인덱스 배열과 연관 배열 두 가지 형태로 사용할 수 있습니다.
// 인덱스 배열
$numbers = [1, 2, 3, 4, 5];

// 연관 배열
$person = [
  'name' => 'John',
  'age' => 30,
  'email' => 'john@example.com'
];
  1. 객체 (Object): 클래스로부터 생성된 인스턴스를 나타내는 데이터 타입입니다. 객체는 속성(프로퍼티)와 메서드로 구성됩니다.
class Person {
  public $name;
  public $age;

  public function __construct($name, $age) {
    $this->name = $name;
    $this->age = $age;
  }

  public function greet() {
    echo "Hello, my name is " . $this->name . " and I'm " . $this->age . " years old.";
  }
}

$person = new Person("John", 30);
$person->greet();
  1. 리소스 (Resource): 외부 자원에 대한 참조를 나타내는 데이터 타입입니다. 예를 들어, 파일 핸들, 데이터베이스 연결 등이 리소스로 표현될 수 있습니다.
// 파일 핸들링 예시
$file = fopen("example.txt", "r");
// 파일 핸들 $file을 리소스로 표현

// 데이터베이스 연결 예시
$connection = mysqli_connect("localhost", "username", "password", "database");
// 데이터베이스 연결 리소스 $connection
  1. callable: 함수 또는 메서드를 참조할 수 있는 데이터 타입입니다. 함수명, 익명 함수, 클래스의 정적 메서드 등이 callable 타입으로 사용될 수 있습니다.
// 함수 참조
$functionRef = 'strlen';
echo $functionRef("Hello");  // 문자열 길이 반환

// 익명 함수
$anonymousFunc = function($x, $y) {
  return $x + $y;
};
echo $anonymousFunc(5, 3);  // 8 반환

// 클래스의 정적 메서드
class Math {
  public static function add($x, $y) {
    return $x + $y;
  }
}
$methodRef = [Math::class, 'add'];
echo $methodRef(4, 2);  // 6 반환
  1. iterable: 반복 가능한 데이터 타입을 나타내는 인터페이스입니다. 배열이나 객체의 경우 반복 가능한 타입으로 사용할 수 있습니다. iterable 타입은 foreach 루프와 같은 반복 작업에 사용됩니다.
// 배열을 반복 가능한 타입으로 사용
$numbers = [1, 2, 3, 4, 5];
foreach ($numbers as $number) {
  echo $number . " ";
}

// 객체를 반복 가능한 타입으로 사용
class MyIterator implements Iterator {
  private $position = 0;
  private $data = ['A', 'B', 'C'];

  public function rewind() {
    $this->position = 0;
  }

  public function current() {
    return $this->data[$this->position];
  }

  public function key() {
    return $this->position;
  }

  public function next() {
    ++$this->position;
  }

  public function valid() {
    return isset($this->data[$this->position]);
  }
}

$myIterator = new MyIterator();
foreach ($myIterator as $key => $value) {
  echo $key . ": " . $value . " ";
}

isFileNotSelected

public function isFileNotSelected(): bool {
    return empty($_FILES['upload']['name']);
  }
  • isFileNotSelected 메서드는 현재 업로드된 파일이 선택되지 않은 경우를 확인하는 함수입니다.

  • 해당 메서드는 $_FILES['upload']['name'] 값을 확인하여 파일 이름이 비어있는지를 검사합니다.

  1. $_FILES['upload']는 PHP의 슈퍼글로벌 변수 중 하나로, 파일 업로드 시에 전송된 파일에 대한 정보를 담고 있습니다.

  2. 'name'은 업로드된 파일의 원본 이름을 나타냅니다.

  • empty($_FILES['upload']['name'])$_FILES['upload']['name'] 값이 비어있으면 true를 반환하고, 그렇지 않으면 false를 반환합니다.

  • 따라서, isFileNotSelected 메서드는 업로드된 파일이 선택되지 않은 경우 true를 반환하고, 선택된 경우에는 false를 반환합니다.

isValidExtension

  public function isValidExtension(string $extension): bool {
    return in_array($extension, $this->allowedExtensions);
  }
  • isValidExtension 메서드는 주어진 파일 확장자가 허용된 확장자 목록에 포함되어 있는지를 확인하는 함수입니다.

  • 해당 메서드는 $extension 매개변수와 $this->allowedExtensions 속성을 비교하여 주어진 확장자가 허용된 확장자인지를 판별합니다.

  1. $extension은 확인할 파일의 확장자를 나타냅니다.

  2. $this->allowedExtensions는 $this 객체의 allowedExtensions 속성으로, 허용된 확장자의 배열을 나타냅니다.

  • in_array($extension, $this->allowedExtensions)$extension 값이 $this->allowedExtensions 배열에 포함되어 있는지를 확인합니다.

  • in_array() 함수는 주어진 값이 배열에 존재하는지를 확인하고, 존재할 경우 true를 반환하고, 그렇지 않을 경우 false를 반환합니다.

따라서, isValidExtension 메서드는 주어진 확장자가 허용된 확장자 목록에 포함되어 있는 경우 true를 반환하고, 포함되지 않은 경우에는 false를 반환합니다.

isValidSize

 public function isValidSize(int $size): bool {
    $maxSize = 1000000;
    return $size <= $maxSize;
  }
  • isValidSize 메서드는 주어진 파일 크기가 유효한 크기인지를 확인하는 역할을 합니다.

  • 매개변수로 받은 $size를 $maxSize와 비교하여 유효한 크기인지를 판단합니다. 여기서 $maxSize는 1000000로 설정되어 있습니다.

  • 함수는 주어진 파일 크기 $size가 $maxSize 이하인 경우 true를 반환하고, 그렇지 않은 경우에는 false를 반환합니다. 이를 통해 파일 크기가 허용 범위 내에 있는지를 확인할 수 있습니다.

moveUploadedFile

  public function moveUploadedFile(string $tmp_name, string $target_dir): bool {
    return move_uploaded_file($tmp_name, $target_dir);
  }
  • moveUploadedFile() 함수는 업로드된 파일지정된 대상 디렉토리이동시키는 역할을 합니다. 이 함수는 move_uploaded_file() 내장 함수호출하여 파일 이동을 수행합니다.

  • 함수는 두 개매개변수를 받습니다. 첫 번째 매개변수인 $tmp_name은 업로드된 파일의 임시 경로를 나타내며, 두 번째 매개변수인 $target_dir은 파일이 이동될 대상 디렉토리를 나타냅니다.

  • move_uploaded_file($tmp_name, $target_dir) 함수는 업로드된 파일을 임시 경로에서 대상 디렉토리로 이동시킵니다. 이 과정에서 파일의 권한과 소유자도 유지됩니다. 함수는 파일 이동에 성공하면 true를 반환하고, 파일 이동에 실패하면 false를 반환합니다.

  • 이 코드에서는 move_uploaded_file() 함수의 반환 값을 그대로 반환하므로, 파일 이동에 성공했는지 여부를 호출한 곳에서 확인할 수 있습니다.

profile
어제보다 오늘 그리고 오늘 보다 내일...

0개의 댓글