Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [PHP]Klasa uploadera
Forum PHP.pl > Forum > Przedszkole
Sagnitor
Witam!
Niedawno miałem za zadanie napisać mały panel administracyjny dla pewnej szkoły. Panel ten miał posiadać m. in. możliwość uploadu plików na serwer. Po krótkim zastanowieniu przyszła mi myśl napisania klasy uploadera - narzędzia do uploadowania plików. Klasa była pisana na szybko w związku z tym chciałbym poddać ją do konsultacji z wami.
Zastanawiam się czego jej jeszcze brakuje, jakie posiada błędy, co można poprawić. Zaznaczam, że chciałbym jej używać także w przyszłych projektach.
Przede wszystkim chodzi o bezpieczeństwo i elastyczność.
Oto kod:

  1. class CUploader
  2. {
  3. private $aFILES = array(); // Bufor plików
  4. private $AllowedEx; // Dozwolone rozszerzenia
  5.  
  6. private $path;
  7. private $MaxSize; // Maksymalny rozmiar pliku
  8. private $nFiles = 0; // Ilość plików w buforze
  9.  
  10. //Modes
  11. private $mode_fip; // Tryb tworzący ścieżkę z katalogiem o nazwie pliku
  12. private $mode_fun; // Tryb bez buforowania
  13. private $mode_cd; // Tryb czyszczenia docelowego katalogu
  14.  
  15. //Temp variables
  16. private $bTrueEx; // Zgodność rozszerzenia dodawanego pliku
  17. private $uploadedFiles; // Pliki wrzucone przez bufor na serwer
  18.  
  19. public function __construct($sPath = UPLOAD_DIR, $Extensions = NULL, $nMaxSize = 1048576)
  20. {
  21. $this->path = $sPath;
  22. $this->MaxSize = $nMaxSize;
  23. if(is_array($Extensions) === TRUE)
  24. $this->AllowedEx = $Extensions;
  25. }
  26.  
  27. public function config(array $config = array())
  28. {
  29. if(isset($config['MODE_FIP']))
  30. $this->mode_fip = (bool) $config['MODE_FIP'];
  31. if(isset($config['MODE_FUN']))
  32. $this->mode_fun = (bool) $config['MODE_FUN'];
  33. if(isset($config['MODE_CD']))
  34. $this->mode_cd = (bool) $config['MODE_CD'];
  35. return $this;
  36. }
  37.  
  38. public function changeDir($sDir)
  39. {
  40. if($sDir !== NULL) {
  41. $this->path = $sDir;
  42. return $this;
  43. }
  44. else return false;
  45. }
  46.  
  47. public function setAllowedEx($Extensions = NULL)
  48. {
  49. if(is_array($Extensions) == TRUE)
  50. {
  51. $this->AllowedEx = $Extensions;
  52. return $this;
  53. }
  54. }
  55.  
  56. public function clearBuffer()
  57. {
  58. for($i=0; $i < count($aFILES); ++$i)
  59. {
  60. unset($aFILES[$i]);
  61. }
  62. return $this;
  63. }
  64.  
  65. public function addFile($File)
  66. {
  67. if($this->checkEx($File['name']) === TRUE) // Sprawdzenie rozszerzenia
  68. {
  69. if($this->checkSize($File['size']) === TRUE) // Sprawdzenie rozmiaru
  70. {
  71. if($this->mode_fun === TRUE) // Tryb FILE_UP_NOW
  72. $this->upload($File);
  73. else {
  74. $this->aFILES[$this->nFiles] = $File;
  75. $this->nFiles++;
  76. }
  77. return true;
  78. } else return false;
  79. } else return false;
  80. }
  81.  
  82. // Funkcja służąca upload'owi plików z bufora na serwer. Uchwyt FileHandle wykorzystywany w razie wyłączonego buforowania.
  83. // Domyślnie ustawiony na 0 (wyłączony).
  84. public function upload($FileHandle = 0)
  85. {
  86. if($FileHandle === 0 && $this->aFILES !== NULL)
  87. {
  88. for($i=0; $i < count($this->aFILES); ++$i)
  89. {
  90. if(is_uploaded_file($this->aFILES[$i]['tmp_name']))
  91. {
  92. if ($this->mode_fip === true) // Tryb FILE_IN_PATH
  93. $this->path = $this->path.(pathinfo($this->aFILES[$i]['name'], PATHINFO_FILENAME)).'/';
  94.  
  95. if($this->mode_cd === true) // Tryb CLEAR_DIRECTION
  96. $this->clearDir($this->path); // W tym momencie $this->path wskazuje katalog docelowy
  97.  
  98. $this->path = $this->path.$this->aFILES[$i]['name']; // Utworzenie pełnej ścieżki z nazwą pliku.
  99. if( move_uploaded_file($this->aFILES[$i]['tmp_name'], $this->path) )
  100. {
  101. $this->uploadedFiles++;
  102. chmod($this->path, 0600);
  103. }
  104. }
  105. }
  106. return ($this->nFiles === $this->uploadedFiles ? true : false);
  107. } elseif ($FileHandle !== 0) {
  108. if(is_uploaded_file($FileHandle['tmp_name']))
  109. {
  110. if($this->mode_fip === true) // Tryb FILE_IN_PATH
  111. $this->path = $this->path.(pathinfo($FileHandle['name'], PATHINFO_FILENAME)).'/';
  112.  
  113. if($this->mode_cd === true) // Tryb CLEAR_DIRECTION
  114. $this->clearDir($this->path); // W tym momencie $this->path wskazuje katalog docelowy
  115.  
  116. $this->path = $this->path.$this->$FileHandle['name'];
  117. if(move_uploaded_file($FileHandle['tmp_name'], $this->path))
  118. {
  119. chmod($this->path, 0600);
  120. return true;
  121. } else return false;
  122. } else return false;
  123. } else return false;
  124.  
  125. }
  126.  
  127. private function checkEx ($sFileName)
  128. {
  129. if($this->AllowedEx !== NULL)
  130. {
  131. $this->bTrueEx = FALSE;
  132. for($i=0; $i < count($this->AllowedEx); ++$i)
  133. {
  134. if($this->AllowedEx[$i] === pathinfo($sFileName, PATHINFO_EXTENSION)) // Jeżeli rozszerzenie pliku znajduje się w tablicy dostępnych rozszerzeń - TRUE
  135. $this->bTrueEx = TRUE;
  136. }
  137. return ($this->bTrueEx === TRUE ? TRUE : FALSE);
  138. }
  139. }
  140.  
  141. private function checkSize($nValue)
  142. {
  143. return ($nValue <= $this->MaxSize ? TRUE : FALSE);
  144. }
  145.  
  146. private function clearDir($sDir)
  147. {
  148. if($handle = opendir("$sDir"))
  149. {
  150. while(false !== ($file = readdir($handle)))
  151. {
  152. if($file!='.' && $file!='..')
  153. unlink("$sDir$file");
  154. }
  155. closedir($handle);
  156. }
  157. return true;
  158. }
  159. }


Jak widać klasa nie jest jeszcze ukończona i perfekcyjna, ale chciałbym się dowiedzieć czy w dobrym kierunku piszę ten kod.
Może pokrótce omówię jak to powinno działać.

Argumentami konstruktora są opcjonalnie ścieżka upload'u (domyślnie główny katalog), tablica zezwolonych rozszerzeń (możemy wybrać z dostępnych 'IMAGE', 'APPLICATION' itd., lub stworzyć własną $Ex = array('cokolwiek', 'cokolwiek2'); ) oraz maksymalny rozmiar przesyłanego pliku.
Cały upload przedstawię za pomocą schematu:

NOWY UPLOADER -> OKREŚLENIE GŁÓWNYCH PARAMETRÓW -> KONFIGURACJA TRYBÓW -> DODANIE PLIKU DO BUFORA (wielokrotne) -> UPLOAD PLIKÓW Z BUFORA

Za cel stawiałem sobie możliwość dodawania dowolnej ilości plików do bufora (tzw. multiupload).
Jeśli chodzi o tryby, jeszcze pozostawiłem ten moduł do dopracowania. Jak na razie dostępne są dwa tryby (nie wiem czy zbyt sensowne):
- MODE_FUN (FILE_UP_NOW) - tryb z wyłączonym buforowaniem. Pliki są od razu uploadowane z wywołaniem funkcji addFile();
- MODE_FIP (FILE_IN_PATH) - tryb z uploadowaniem pliku do katalogu o nazwie pliku, np. ./katalog/katalog2/podanie_11/podanie11.php

Z góry dzięki za uzasadnioną krytykę oraz uwagi.

Pozdrawiam

EDIT:
Uaktualniłem klasę, dodałem kilka poprawek oraz nowy mod MODE_CD (CLEAR_DIRECTION), który odpowiada za czyszczenie docelowego katalogu przed uploadowaniem plików. Planuję jeszcze dorobić obsługę wyjątków, a tym samym komunikatów i błędów. Już edytując ten post zauważyłem kilka niedopatrzeń, idę je poprawiać. ;-)
zibihehe
Wszytko pięknie, ładnie ale czy jest się męczyć? Ja używam uploadify, całkiem nieźle się spisuje, przejrzysty, łatwo się go zespala z całością, dobrze dopracowany. http://www.uploadify.com/documentation wink.gif
Sagnitor
Wiesz, ja mam nadmiar wolnego czasu ;-)

Po prostu chcę stworzyć coś bezpiecznego i elastycznego, co przyda mi się w takich małych projektach.
Jeśli chodzi o męczenie to może jestem odmianą jakiegoś nerda, ale mi to sprawia przyjemność i radochę. ^^

Pozdrawiam
potreb
  1. if($Extensions == 'IMAGE')
  2. $this->AllowedEx = array("gif", "jpg", "png", "bmp", "tiff", "rgb", "ras", "ico");
  3. if($Extensions == 'AUDIO')
  4. $this->AllowedEx = array("wav", "mp3", "mp2", "mid", "rpm", "ra");
  5. if($Extensions == 'APPLICATION')
  6. $this->AllowedEx = array("pdf", "zip", "mdb", "xls", "swf", "ppt", "doc", "tar");
  7. if($Extensions == 'VIDEO')
  8. $this->AllowedEx = array("avi", "movie", "qt", "mpeg");
  9. if($Extensions == 'TEXT')
  10. $this->AllowedEx = array("css", "html", "htm", "txt", "xml", "rtf", "sgml");
  11. if($Extensions == 'MODEL')
  12. $this->AllowedEx = array("igs", "mesh" ,"wrl", "msh");
  13. if(is_array($Extensions) == TRUE)
  14. $this->AllowedEx = $Extensions;


Po co ci ten wycinek. Gdy tworzysz obiekt klasy upload dodajesz właściwość z rozszerzeniami plików jakie możesz wysłać.
  1. $upload = new upload($_FILES['upload']);
  2. $ext = $upload->ext(array('jpg', 'png', 'gif');
  3. $filesize = $upload->filesize(20);


Dodatkowo jakbyś jeszcze miał możliwość sprawdzania typów mime. Zauważ, że nie masz takiego czegoś jak zwracanie błędów tudzież komunikatów.
Również możesz ustawić sobie opcję, gdy będzie taki sam plik już wysłany to czy mu zmieniać automatycznie nazwę dodając do końca np _2, _3, przy wyłączonej opcji będzie go zastępować. I ważna opcja: wywalanie z nazwy plików dziwnych znaków, zostawiasz tylko litery, liczbym _ . itd.
Sagnitor
Dzięki za cenną poradę.

Myślałem już nad sprawdzaniem typów MIME, lecz jak wiadomo pochodzą one od użytkownika, a wszystko co otrzymujemy od użytkownika należy uważać za niebezpieczne. ;-)
Czytałem o FileInfo i funkcji finfo_file(), niestety mam problem co do instalacji tego rozszerzenia PHP.

Błędy i komunikaty - aktualnie nad nimi pracuję, zastanawiam się w jakiej postaci je zwracać, jednak tutaj jestem na dobrej drodze.
Jestem bardzo wdzięczny za propozycje kolejnych 'trybów' uploadu, już niedługo dodam je do klasy, logiczne propozycje.
Wpadł mi do głowy taki pomysł trybu, w którym katalog do którego dodawany jest plik byłby czyszczony przez skrypt (taka jakby aktualizacja).

Czekam na kolejne propozycje.

Pozdrawiam
bastard13
Małe sugestie:)
1) Staraj się używać === zamiast == oraz !== zamiast !=, te pierwsze porównują również typ zmiennej
2) Usuń wszystkie wywołania funkcji echo(). Ta klasa ma służyć do uploadu plików, więc echo nie ma tam racji bytu. Zamiast tego albo wyrzucaj wyjątek (może być twój własny np. CUploader_Exception) albo zwracaj kod błędu lub false, czy co tam innego wymyślisz:)
3) Do metod config() i changeDir() możesz sobie dodać na końcu: return $this; Dzięki temu będziesz mógł zrobić tak:
  1. $cuploader->config(/*params*/)->changeDir(/*params*/);

zamiasta
  1. $cuploader->config(/*params*/);
  2. $cuploader->changeDir(/*params*/);

W sumie to samo możesz zrobić w clearBuffer().
4) addFile() mogłoby zwracać true w przypadku, gdy wszystko się udało.
5) metoda upload():
a) masz if ... elseif ..., więc może na samym końcu dodaj return false (true? exception?), bo teraz jak dojdzie do końca, a nie wejdzie do bloków instrukcji, to się nic nie stanie.
cool.gif pętla for wykona się tylko dla jednego elementu, bo zwracasz false lub true wewnątrz niej. Powinieneś mieć jakąś zmienną $uploadAll = true i każdy swój return wewnątrz pętli zastąp $uploadAll = $uploadAll && (boolean)
6) Dodaj sobie komentarze do metod publicznych, bo z doświadczenia wiem, że po pewnym czasie zapomina się od czego był ten cudowny parametr $FileHandle i dlaczego defaultowo jest ustawiony na zero:)
7)
  1. config($sModeFirst = NULL, $sModeSecond = NULL, $sModeThird = NULL, $sModeFourth = NULL, $sModeFifth = NULL)

Rozumiem, że tyle parametrów jest dlatego, że planujesz rozszerzyć listę ustawieńsmile.gif
Proponuje zrobić tak:
  1. config(array $config = array())
  2. {
  3. if(isset($config['MOD_FLIP'])
  4. $this->MOD_FLIP = (bool) $config['MOD_FLIP'];
  5. //i tak dla każdej opcji
  6. }

unikasz przekazywania setek parametrów, pętli for, switch'a. Dodanie kolejnej opcji, to też nie jest duża zmiana w kodzie klasy.
8) MODE_FIP i MODE_FUN powinny być pisane z małych liter. Wielkimi literami nazywa się stałe.
Sagnitor
Nie wiem jak Ci się odwdzięczyć, wink.gif
Ogromne dzięki, za pomoc w poprawie jakże istotnych spraw w klasie. Klasa jest teraz bardziej elastyczna w rozbudowie i samym użyciu.
Mam tylko pewne zastrzeżenie do punktu nr 7.

W owej radzie poleciłeś mi zastosowanie parametru jako tablicy, czyli tym samym konfiguracja klasy sprowadza się do tablicy:

  1. $Uploader = new CUploader('./', $Ex = array("pdf"));
  2.  
  3. $aConfig = array();
  4. $aConfig['MODE_FIP'] = true;
  5. $aConfig['MODE_FUN'] = true;
  6.  
  7. $Uploader->config($aConfig);


Mam jednak pytanie i dążę do rozwiązania tego w taki sposób, aby efekt był podobny jak w funkcji wbudowanej unset(). Mam na myśli dowolną ilość podawanych zmiennych, bez wykorzystania tablicy. Chciałbym, aby konfiguracja klasy wyglądała mniej więcej tak:

  1. $Uploader = new CUploader('./', $Ex = array("pdf"));
  2.  
  3. $Uploader->config(MODE_FIP, MODE_FUN, OTHER_MODE); // i tutaj kolejne 'mody'


Myślę, że takie rozwiązanie bardziej 'uprzyjemnia' samo korzystanie z klasy.

Pozdrawiam i dziękuję za dotychczasowe odpowiedzi
bastard13
Nie wiem, czy przekazywanie do metody zbyt wielu parametrów, jest dobrym pomysłem.
Możesz dodać jedną metodę np. setMode, gdzie pierwszym parametrem byłaby nazwa trybu, a drugim - wartość (domyślnie true smile.gif. Dla przyjemniejszego korzystania dodałbym tam (w ciele metody) jeszcze return $this;
I wtedy zamiast:
  1. $Uploader->config(MODE_FIP, MODE_FUN, OTHER_MODE);

Miałbyś:
  1. $Uploader
  2. ->setMode(MODE_FIP)
  3. ->setMode(MODE_FUN)
  4. ->setMode(OTHER_MODE);


Dodatkowo te twoje tryby to był trochę przeprojektował na zasadzie:
1) Nazwy trybów zamieniłbym na consty (wewnątrz klasy):
  1. const MODE_FIP = 1;
  2. const MODE_FUN = 2;
  3. const OTHER_MODE = 4;

2) Dodałbym tablice z ustawieniami trybów:
  1. private $_config = array(
  2. MODE_FIP => false,
  3. MODE_FUN => false,
  4. OTHER_MODE => false
  5. );

I tak ustawienie mode_fip na true wyglądałoby:
  1. $Uploader->setMode(CUploader::MODE_FIP); //bez drugiego parametru, bo defaultowo jest true


Jeżeli oczywiście nadal chcesz przekazywać niesprecyzowaną (bo tak naprawdę nie wiesz ile trybów będziesz obsługiwał) ilość parametrów, to radzę się zainteresować funkcjami:
http://php.net/manual/en/function.func-get-args.php
http://www.php.net/manual/en/function.func-num-args.php
Dzięki nim unikniesz modyfikacji metody config() za każdym razem, gdy zmieni się liczba dostępnych trybów.

Możesz jeszcze użyć metody __call() do obsługi ustawiania trybów (setModeFip(), setModeFun(), setOtherMode(), etc.), ale osobiście staram się unikać magii w kodzie:P
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.