Witaj Gościu! ( Zaloguj | Rejestruj )

Forum PHP.pl

> [Symfony2][Symfony] file upload nie podoba mi się
Foxx
post
Post #1





Grupa: Zarejestrowani
Postów: 896
Pomógł: 76
Dołączył: 15.11.2003
Skąd: Sosnowiec/Kraków

Ostrzeżenie: (0%)
-----


Właśnie po raz pierwszy zaimplementowałem upload obrazka + Doctrine i mam pewne wątpliwości. Chodzi o nakład pracy w kodzie potrzebny do przeprowadzenia tej operacji. Zrobiłem to w oparciu o lifecycle callbacks i musiały powstać 3 pola i 11 metod w obiekcie. Fajnie, że nie trzeba nic robić w kontrolerze i rozumiem, że część tych metod to rzeczy w stylu getWebPath i jak mi się nie podoba to nie muszę koniecznie ich używać, ale mimo wszystko to wydaje mi się trochę dziwne. Strach pomyśleć co się stanie jak będę miał 6 plików przy obiekcie.

A na koniec jeszcze czytam w cookbook, że "The PreUpdate and PostUpdate callbacks are only triggered if there is a change in one of the entity's field that are persisted" więc jak zmienię tylko awatar w profilu usera to on mi się nie zapisze :/ Trochę to załamujące, czy to podejście z cookbook jest naprawdę optymalne?

Ten post edytował Foxx 17.04.2013, 13:48:09
Go to the top of the page
+Quote Post
 
Start new topic
Odpowiedzi
Crozin
post
Post #2





Grupa: Zarejestrowani
Postów: 6 476
Pomógł: 1306
Dołączył: 6.08.2006
Skąd: Kraków

Ostrzeżenie: (0%)
-----


Przykłady uploadu w dokumentacji formularzu Symfony są fatalne - gwałcą kilka podstawowych zasad "dobrego kodu", które sam framework mocno forsuje. Nigdy nie powinny się tam znaleźć w takiej formie.

1. Utwórz odpowiedni model:
1.1. Obiekt reprezentujący wgrywany plik:
  1. namepsace ...\Form\Domain;
  2.  
  3. class Image {
  4. /**
  5.   * @Assert\NotBlank
  6.   * @Assert\Image(minWidth=50, maxWidth=200, ..., groups={"UserAvatar"})
  7.   * @Assert\Image(minWidth=500, maxWidth=5000, ..., groups={"UserProfile"})
  8.   */
  9. private $file;
  10.  
  11. /**
  12.   * @Assert\Type("boolean")
  13.   */
  14. private $delete = false;
  15.  
  16. // gettery/settery i cokolwiek uznasz za stosowne
  17. }
1.2. W obiekcie, który posiada ów obraz (powiedzmy, że rozważamy klasyczny przykład użytkownika i jego awataru) najczęściej chcemy przechować jedynie ścieżkę do pliku. Ewentualnie możemy mieć kompletnie osobną tabelę w bazie danych reprezentującą obraz oraz metadane jego dotyczące (wymiary, typ itp.) złączony z inną tabelą relacją jeden-do-jednego. W obu przypadkach mechanizm jest jednak taki sam, więc omówimy jedynie ten pierwszy:
  1. namespace ...\Entity;
  2.  
  3. class User {
  4. private $id;
  5. private $username;
  6. private $password;
  7.  
  8. private $avatarPath;
  9. }
Obiekt domeny nie powinien mieć absolutnie nic wspólnego z uploadem plików!
1.3. Na koniec potrzebujemy osobny obiekt, który złączy to wszystko razem, tj. wgrywany plik oraz interesujący nas obiekt sam w sobie:
  1. namespace ...\Form\Domain;
  2.  
  3. class ImageUpload {
  4. /** @Assert\Valid(deep=true) */
  5. private $object;
  6.  
  7. /** @Assert\Valid(deep=true) */
  8. private $image;
  9.  
  10. private $imagePath;
  11.  
  12. public function __construct($object, Image $image, $imagePath) {
  13. ...
  14. }
  15. }
$object to obiekt do którego będziemy wgrywać zdjęcie, $image to samo zdjęcie, a $imagePath to ścieżka do właściwości przechowującej ścieżkę do wgrywanego pliku (patrz: komponent PropertyAccess Symfony). Czyli w tym przypadku ścieżką taką będzie "object.avatarPath" (właściwie to pierwszy człon "object." można pominąć). Jeżeli nasz obiekt byłby bardziej złożony i tak możemy bez problemu określić ścieżkę do właściwości "...path", np.: "object.image.path", "object.profile.defaultImage" itp.
1.4. Zarówno obiekt Image jak i ImageUpload mogą być wykorzystywane z dowolnym innym obiektem (User, Profile, Photo, Album itp.)
2. Model jest już przygotowany, pozostało obsłużenie samego uploadu. Właściwie to samym uploadem zajmje się symfony, my musimy jedynie zadbać o to by z wgranego pliku ("image.image") ścieżka została przepisana do interesującej nas właściwości obiektu domeny (tutaj: "object.avatarPath"). To zadanie musi wykonać się po walidacji formularza i przed pobraniem z niego danych w celu dalszej obróbki: FormEvents::POST_BIND.
2.1. Tworzymy sobie usługę, którą rejestrujemy jako listenera dla w/w zdarzenia. Ta usługa, bez problemu może w swoim konstruktorze otrzymać ścieżkę do katalogu dla uploadu czy jakąś usługę typu FilesystemUtility, która wygeneruje unikalną nazwę dla pliku. Mając już takiego listnera możemy spokojnie przerzucić dane:
  1. public function postBind(FormEvent $event) {
  2. $data = $event->getData();
  3.  
  4. if (!$data instanceof ImageUpload) {
  5. return;
  6. }
  7.  
  8. if ($data->getImage()->delete()) {
  9. // usuwamy plik i aktualizujemy nasz obiekt
  10. PropertyAccess::getPropertyAccessor()->setValue($data->getObject(), null);
  11.  
  12. return;
  13. }
  14.  
  15. // Tutaj wykonujemy zapis pliku w odpowiednym miejscu, a korzystając z ProeprtyAccess ustawiamy ścieżkę do pliku:
  16. PropertyAccess::getPropertyAccessor()->setValue($data->getObject(), $data->getImagePath());
  17. }

3. Obsługa tego w jakimś kontrolerze:
  1. $user = ...;
  2.  
  3. $data = new ImageUpload($user, new Image(), 'object.avatarPath');
  4. $form = $this->createForm(new SomeFormType(), $data);
  5.  
  6. if (request post) {
  7. $form->bindRequest($request);
  8.  
  9. if ($form->isValid()) {
  10. $formData = $form->getData()->getObject();
  11.  
  12. // ...
  13. }
  14. }


Plusy takiego rozwiązania:
1. Jest uniwersalne, można je wykorzystać z dowolnym obiektem.
2. Wszystko jest ładnie rozdzielone, mamy czysty kod.

Jeżeli chciałbyś dodać obsługę wielu plików, wystarczy że zmodyfikujesz powyższe w taki sposób by ImageUpload posiadał kolekcję obiektów Image oraz tablicę $imagePath.

Ten post edytował Crozin 17.04.2013, 16:17:23
Go to the top of the page
+Quote Post

Posty w temacie


Reply to this topicStart new topic
2 Użytkowników czyta ten temat (2 Gości i 0 Anonimowych użytkowników)
0 Zarejestrowanych:

 



RSS Aktualny czas: 29.12.2025 - 20:10