Page 1 of 1
Usuwanie plików i download plików
#1
Posted 04 April 2012 - 06:34 AM
Witam.
Mam 2 pytania bo nijak nie mogę sobie poradzić, przejrzałem dużo postów ale nie znalazłem odpowiedniego rozwiązania.
1. W jaki sposób usunąć fizyczne plik z dysku wraz z usunięciem rekordu z bazy wykorzystując sposób jaki jest zawarty w standardowej procedurze usuwania rekordów w kontrolerze. W jednym polu tabeli mam nazwę tego pliku, np.: plik.zip
2. W jaki sposób mogę wykonać download plików tak aby adres pobieranego pliku nie był widoczny wprost. Tak jak powyżej nazwa pliku jest także w polu tabeli.
Mam 2 pytania bo nijak nie mogę sobie poradzić, przejrzałem dużo postów ale nie znalazłem odpowiedniego rozwiązania.
1. W jaki sposób usunąć fizyczne plik z dysku wraz z usunięciem rekordu z bazy wykorzystując sposób jaki jest zawarty w standardowej procedurze usuwania rekordów w kontrolerze. W jednym polu tabeli mam nazwę tego pliku, np.: plik.zip
2. W jaki sposób mogę wykonać download plików tak aby adres pobieranego pliku nie był widoczny wprost. Tak jak powyżej nazwa pliku jest także w polu tabeli.
#2
Posted 04 April 2012 - 06:54 AM
Witaj,
1) Klasa CActiveRecord ma metodę afterDelete(). Musisz w swoim modelu dodać tą metodę i w niej napisać to co ma się dziać po usunięciu rekordu z bazy.
2) To jest chyba proste :-)
Masz tabelę "files". W rekordzie o Id=1 masz pole z nazwą pliku "plik.zip".
Tworzysz np. akcję "download" w kontrolerze "FilesController", która przyjmuje parametr $id.
Wtedy sprawdzasz czy w bazie jest taki rekord, odczytujesz nazwę pliku i przesyłasz jego zawartość do przeglądarki (musisz dać też odpowiedni header, aby otworzyło się okno do zapisu pliku - sprawdź przykład na tej stronie:
http://php.net/manua...on.readfile.php)
Pozdrawiam Mariusz
1) Klasa CActiveRecord ma metodę afterDelete(). Musisz w swoim modelu dodać tą metodę i w niej napisać to co ma się dziać po usunięciu rekordu z bazy.
2) To jest chyba proste :-)
Masz tabelę "files". W rekordzie o Id=1 masz pole z nazwą pliku "plik.zip".
Tworzysz np. akcję "download" w kontrolerze "FilesController", która przyjmuje parametr $id.
Wtedy sprawdzasz czy w bazie jest taki rekord, odczytujesz nazwę pliku i przesyłasz jego zawartość do przeglądarki (musisz dać też odpowiedni header, aby otworzyło się okno do zapisu pliku - sprawdź przykład na tej stronie:
http://php.net/manua...on.readfile.php)
Pozdrawiam Mariusz

#3
Posted 04 April 2012 - 10:55 AM
Co do odpowiedzi na drugie pytanie: readfile() to raczej zły pomysł. Może się sprawdzić dla jakichś małych plików (zdjęcia, pliki tekstowe) jednak przy większych plikach mogą być problemy z wydajnością i duże zużycie ramu.
Lepiej pomyśleć o wykorzystaniu modułu do Apache - X-Sendfile. Minimalnie obciąża serwer a w folderze z plikami można dodać .htaccess z "deny from all". Problem tylko jest taki, że trzeba ten moduł zainstalować na serwerze czyli trzeba mieć dostęp do serwera (np. serwer dedykowany). Na serwerach wirtualnych raczej X-sendfile się nie znajdzie.
Tutaj jest ładnie opisane:
http://www.yiiframew...b-applications/
Lepiej pomyśleć o wykorzystaniu modułu do Apache - X-Sendfile. Minimalnie obciąża serwer a w folderze z plikami można dodać .htaccess z "deny from all". Problem tylko jest taki, że trzeba ten moduł zainstalować na serwerze czyli trzeba mieć dostęp do serwera (np. serwer dedykowany). Na serwerach wirtualnych raczej X-sendfile się nie znajdzie.
Tutaj jest ładnie opisane:
http://www.yiiframew...b-applications/
#4
Posted 04 April 2012 - 02:28 PM
Problem nr 2 rozwiązałem w ten sposób, że:
- w kontrolerze dodałem akcję:
+ uprawnienia do wykonania tej akcji w "public function accessRules()"
oraz w widoku częściowym dałem
Działa i chyba tak może zostać.
Pozostał jeszcze problem nr 1.
Mariusz W. pisze:
ale czy nie potrzeba beforeDelete(), żeby chociaż do zmiennej przypisać jaki plik ma usunąć?
- w kontrolerze dodałem akcję:
public function actionDownload($id)
{
$model=$this->loadModel($id);
$file = Yii::app()->basePath . '/../images/' . $model->image;
if (file_exists($file))
{
$fileDir=Yii::app()->basePath . '/../images/';
Yii::app()->request->sendFile(
$model->image,
file_get_contents($fileDir . $model->image),
$model->image
);
}
else
{
throw new CHttpException(404,'Żądany plik nie istnieje.');
return $model;
}
}
+ uprawnienia do wykonania tej akcji w "public function accessRules()"
oraz w widoku częściowym dałem
<?php
echo CHtml::link("Pobierz plik", array('download', 'id'=>$data->id));
?>
Działa i chyba tak może zostać.
Pozostał jeszcze problem nr 1.
Mariusz W. pisze:
Quote
Klasa CActiveRecord ma metodę afterDelete(). Musisz w swoim modelu dodać tą metodę i w niej napisać to co ma się dziać po usunięciu rekordu z bazy.
ale czy nie potrzeba beforeDelete(), żeby chociaż do zmiennej przypisać jaki plik ma usunąć?
#5
Posted 05 April 2012 - 02:37 AM
Witaj,
Nie trzeba, ponieważ metoda afterDelete jest wykonywana na konkretnej instancji modelu, który reprezentuje konkretny rekord. Wygląda to mniej więcej tak:
1) Tworzony jest model i do atrybutów pobierane są wartości z odpowiednich kolumn.
2) Wywoływana jest akcja usuwania rekordu
3) Po usunięciu rekordu w metodzie afterDelete masz dostęp do wartości atrybutów (np. $this->fileName)
Pozdrawiam Mariusz
Nie trzeba, ponieważ metoda afterDelete jest wykonywana na konkretnej instancji modelu, który reprezentuje konkretny rekord. Wygląda to mniej więcej tak:
1) Tworzony jest model i do atrybutów pobierane są wartości z odpowiednich kolumn.
2) Wywoływana jest akcja usuwania rekordu
3) Po usunięciu rekordu w metodzie afterDelete masz dostęp do wartości atrybutów (np. $this->fileName)
Pozdrawiam Mariusz

#6
Posted 05 April 2012 - 05:40 AM
Mariusz W., on 05 April 2012 - 02:37 AM, said:
Witaj,
Nie trzeba, ponieważ metoda afterDelete jest wykonywana na konkretnej instancji modelu, który reprezentuje konkretny rekord. Wygląda to mniej więcej tak:
1) Tworzony jest model i do atrybutów pobierane są wartości z odpowiednich kolumn.
2) Wywoływana jest akcja usuwania rekordu
3) Po usunięciu rekordu w metodzie afterDelete masz dostęp do wartości atrybutów (np. $this->fileName)
Pozdrawiam Mariusz
Nie trzeba, ponieważ metoda afterDelete jest wykonywana na konkretnej instancji modelu, który reprezentuje konkretny rekord. Wygląda to mniej więcej tak:
1) Tworzony jest model i do atrybutów pobierane są wartości z odpowiednich kolumn.
2) Wywoływana jest akcja usuwania rekordu
3) Po usunięciu rekordu w metodzie afterDelete masz dostęp do wartości atrybutów (np. $this->fileName)
Pozdrawiam Mariusz
Thx, za wyjaśnienie. Pomogło.
Dodałem w modelu i działa ładnie. Może komus sie przyda.
protected function afterDelete()
{
parent::afterDelete();
$file = $this->logo;
unlink(Yii::app()->basePath . '/../images/programs/' . $file);
}
#7
Posted 06 April 2012 - 02:09 AM
Witam,
z tym afterDelete, to nie jest taka pewna sprawa.
Problem polega na tym, że z bazy danych - jeżeli użytkownik w bazie ma odpowiednie uprawnienia i nie ma constraint'ów - rekord można zawsze usunąć bo nawet jeżeli odbywa się odczyt lub zapis to są to operacje atomowe. W przypadku usuwaniu pliku z dysku sprawa nie jest już taka pewna. Najprostszy przykład jest taki, że plik może być otwarty w innym skrypcie i usunięcie nie powiedzie się z powodu braku dostępu. Co wtedy? Rekord z bazy usunięty, a plik zostaje.
Lepszą opcją jest próba usunięcia pliku w beforeDelete. W przypadku niepowodzenia wyświetlasz błąd, w przypadku sukcesu usuwasz rekord z bazy danych. To czy użytkownik który łączy się z bazą ma uprawnienia i to czy gdzieś nie ma blokad zależy tylko i wyłącznie od projektu bazy danych i testów wykonanych przed usunięciem (chociażby w beforeDelete
).
z tym afterDelete, to nie jest taka pewna sprawa.
Problem polega na tym, że z bazy danych - jeżeli użytkownik w bazie ma odpowiednie uprawnienia i nie ma constraint'ów - rekord można zawsze usunąć bo nawet jeżeli odbywa się odczyt lub zapis to są to operacje atomowe. W przypadku usuwaniu pliku z dysku sprawa nie jest już taka pewna. Najprostszy przykład jest taki, że plik może być otwarty w innym skrypcie i usunięcie nie powiedzie się z powodu braku dostępu. Co wtedy? Rekord z bazy usunięty, a plik zostaje.
Lepszą opcją jest próba usunięcia pliku w beforeDelete. W przypadku niepowodzenia wyświetlasz błąd, w przypadku sukcesu usuwasz rekord z bazy danych. To czy użytkownik który łączy się z bazą ma uprawnienia i to czy gdzieś nie ma blokad zależy tylko i wyłącznie od projektu bazy danych i testów wykonanych przed usunięciem (chociażby w beforeDelete
---------------------------------------------------------------------
"Never memorize what you can look up in books."
Albert Einstein
"Never memorize what you can look up in books."
Albert Einstein
#8
Posted 06 April 2012 - 02:28 AM
Też zastanawiałem się nad tym, czy lepiej jest usuwać plik w beforeDelete czy afterDelete?
1) Przypadek pesymistyczny w beforeDelete:
usuwany jest plik, natomiast jest problem podczas usuwania rekordu z bazy.
Wtedy gdzieś w systemie nadal pojawia się odniesienie do tego pliku (bo przecież informacja nadal jest w bazie danych), ale nie można nic zrobić, ponieważ plik nie istnieje.
2) Przypadek pesymistyczny w afterDelete
usuwany jest rekord, natomiast wystąpił podczas usuwania pliku (np. tak jak pisałeś, inny skrypt działa na tym pliku). Nie ma wtedy problemu, że plik pokazuje się gdzieś tam w systemie. Natomiast mamy "porzucony" plik.
W tym przypadku można zapisać, że wystąpił taki problem do jakiegoś pliku, a pracownik monitorujący błędy, które występują w aplikacji wyłapie problem :-)
3) Przypadek eliminujący problem
Zapisywać pliki w bazie :-) Łatwiesza synchronizacja. Nie mam w tym zakresie odpowiedniego doświadczenia i wiedzy, ale pewnie baza jest wtedy bardziej obciążona. Jednak na pewno istnieją rozwiązania, które eliminują i ten problem (w końcu całe bazy są przechowywane także w plikach).
1) Przypadek pesymistyczny w beforeDelete:
usuwany jest plik, natomiast jest problem podczas usuwania rekordu z bazy.
Wtedy gdzieś w systemie nadal pojawia się odniesienie do tego pliku (bo przecież informacja nadal jest w bazie danych), ale nie można nic zrobić, ponieważ plik nie istnieje.
2) Przypadek pesymistyczny w afterDelete
usuwany jest rekord, natomiast wystąpił podczas usuwania pliku (np. tak jak pisałeś, inny skrypt działa na tym pliku). Nie ma wtedy problemu, że plik pokazuje się gdzieś tam w systemie. Natomiast mamy "porzucony" plik.
W tym przypadku można zapisać, że wystąpił taki problem do jakiegoś pliku, a pracownik monitorujący błędy, które występują w aplikacji wyłapie problem :-)
3) Przypadek eliminujący problem
Zapisywać pliki w bazie :-) Łatwiesza synchronizacja. Nie mam w tym zakresie odpowiedniego doświadczenia i wiedzy, ale pewnie baza jest wtedy bardziej obciążona. Jednak na pewno istnieją rozwiązania, które eliminują i ten problem (w końcu całe bazy są przechowywane także w plikach).

#9
Posted 06 April 2012 - 05:02 AM
Przypadek 1: można wyeliminować poprzez odpowiedni test przed usunięciem pliku (na przykład sprawdzając czy istnieją rekordy które mogłyby zablokować usunięcie).
Przypadek 2: należy upewnić się, że plik będzie usuwany tylko w jednym miejscu. Operacje rename, unlink mogą wywalić błędy jeżeli np. pierwszy skrypt otworzy plik, drugi skrypt wywoła rename, pierwszy skrypt zamknie plik i wywoła rename albo unlink. Nawet wywołanie file_exist() tuż przed unlink/rename nic nie da, gdyż dalej będą to 2 wywołania nie stanowiące operacji atomowej i prowadzące do sytuacji wyścigu.
Przypadek 3: ok dla mało obciążonych serwisów. Oprócz konieczności przechowywanie wszystkich meta-danych (czas utworzenia, uprawnienia, właściciel itp) jest sprawa wydajności. Pliki z dysku nieraz są (w zależności od serwera) przesyłane asynchronicznie z dysku bezpośrednio do interfejsu sieciowego za pomocą sendfile(). W przypadku db, dochodzi mnóstwo operacji dyskowych.
No i sprawa pieniędzy. Porównaj ceny 1 GB powierzchni dyskowej i ceny 1GB w bazie danych.
Przeczytaj to: http://perspectives....nsOfPhotos.aspx
Przypadek 2: należy upewnić się, że plik będzie usuwany tylko w jednym miejscu. Operacje rename, unlink mogą wywalić błędy jeżeli np. pierwszy skrypt otworzy plik, drugi skrypt wywoła rename, pierwszy skrypt zamknie plik i wywoła rename albo unlink. Nawet wywołanie file_exist() tuż przed unlink/rename nic nie da, gdyż dalej będą to 2 wywołania nie stanowiące operacji atomowej i prowadzące do sytuacji wyścigu.
Przypadek 3: ok dla mało obciążonych serwisów. Oprócz konieczności przechowywanie wszystkich meta-danych (czas utworzenia, uprawnienia, właściciel itp) jest sprawa wydajności. Pliki z dysku nieraz są (w zależności od serwera) przesyłane asynchronicznie z dysku bezpośrednio do interfejsu sieciowego za pomocą sendfile(). W przypadku db, dochodzi mnóstwo operacji dyskowych.
No i sprawa pieniędzy. Porównaj ceny 1 GB powierzchni dyskowej i ceny 1GB w bazie danych.
Przeczytaj to: http://perspectives....nsOfPhotos.aspx
---------------------------------------------------------------------
"Never memorize what you can look up in books."
Albert Einstein
"Never memorize what you can look up in books."
Albert Einstein
#10
Posted 06 April 2012 - 03:45 PM
O, niechcący wywołałem dyskusję.
Pkt. 3 - przechowywanie plików bezpośrednio w bazie wypróbowałem i jest OK w przypadku małych plików a w przypadku dużych obciąża bazę więc przyjąłem obecną metodę.
Pkt 1 lub 2 - chyba afterDelete jest bardziej bezpieczne niż beforeDelete, mimo iż może wystapić błąd że zostanie usunięty rekord z bazy a plik zostanie (w jednym momencie na rekordzie będzie wykonywana jedna operacja więc chyba nie grozi odwołanie do wykonania innej operacji w tym samym czasie).
Pkt. 3 - przechowywanie plików bezpośrednio w bazie wypróbowałem i jest OK w przypadku małych plików a w przypadku dużych obciąża bazę więc przyjąłem obecną metodę.
Pkt 1 lub 2 - chyba afterDelete jest bardziej bezpieczne niż beforeDelete, mimo iż może wystapić błąd że zostanie usunięty rekord z bazy a plik zostanie (w jednym momencie na rekordzie będzie wykonywana jedna operacja więc chyba nie grozi odwołanie do wykonania innej operacji w tym samym czasie).
Share this topic:
Page 1 of 1

Help













