Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [SF2][Symfony2][PHP] Dodawanie rekordu/edycja
Forum PHP.pl > Forum > PHP > Frameworki
Geniesis
Witam ponownie,
niestety znów framework ze mną wygrywa:
1. obecnie mam błąd do rozwiązania z datą podczas zapisu:
użytkownik dodaje post i ma zawartość dodać do bazy danych:

błąd:
  1. FatalErrorException in DateTimeType.php line 53: Error: Call to a member function format() on a non-object


controller:
  1. public function showTopicAction($id, Request $request)
  2. {
  3. // get post for selected topic from db
  4. $postsList = $this->getDoctrine()->getRepository('UserBundle:Posts')->getPostsWithTopicId($id);
  5. //change date format
  6. for($i = 0; $i < count($postsList) ; $i++) {
  7. $postsList[$i][0]->setAddedDate($postsList[$i][0]->getAddedDate()->format('Y-m-d H:i:s'));
  8. $gettedDate = $postsList[$i][0]->getLastEditionDate();
  9. if($gettedDate != null){
  10. $postsList[$i][0]->setLastEditionDate($gettedDate->format('Y-m-d H:i:s'));
  11. }
  12. }
  13. //get user id
  14. $user = $this->getUser();
  15. $userId = ($user !== null) ? $user->getId() : 0;
  16.  
  17. //prepare edit form
  18. $postEnt = new Posts;
  19. $editForm = $this->createForm(EditPostType::class, $postEnt);
  20.  
  21. //prepare add post form
  22. $addPostForm = $this->createForm(AddPostType::class, $postEnt);
  23.  
  24. $addPostForm->handleRequest($request);
  25. //after submit prepare data and save into DB
  26. if ($addPostForm->isSubmitted() && $addPostForm->isValid()) {
  27. $em = $this->getDoctrine()->getManager();
  28. $user = new User;
  29. $user->setId($userId);
  30. $postEnt->setPostAuthor($user);
  31.  
  32. $topic = new Topics;
  33. $topic->setId($id);
  34. $postEnt->setTopic($topic);
  35.  
  36. $postEnt->setAddedDate(new \DateTime());
  37. // $postEnt->setLastEditionDate(new \DateTime());
  38. echo '<pre>';
  39. \Doctrine\Common\Util\Debug::dump($postEnt);
  40. echo '</pre>';
  41. $em->merge($postEnt);
  42. $em->flush();
  43. }
  44.  
  45. return $this->render(
  46. 'UserBundle:Topic:showTopic.html.twig',
  47. 'postsData' => $postsList,
  48. 'userId' => $userId,
  49. 'editForm' => $editForm->createView(),
  50. 'addPostForm' => $addPostForm->createView(),
  51. )
  52. );
  53. }
  54.  


dump wypluwa:
http://thegeniesis.kylos.pl/sf/err1/dump.html
Rozumiem, że pluje się o format daty, jednakże jest deklarowany dla AddedDate - gdzie jeszcze powinienem (lastEditionDate gdy deklaruję to nic nie zmienia)?

2. Pytanie odnośnie edycji
w kontrolerze pobieram dane, chcę tylko dla użytkowników, którzy stworzyli post dać edycję (ukryty div z formularzem, po naciśnięciu przycisku zmienia się display),
tutaj mam problem z wybraniem najlepszej drogi użycia generatora formularzy:
jedna z tych co przychodzi mi do głowy (choć wydaje się trochę przerostem formy):
tworzę pętlę lecącą po wynikach z bazy danych, przy każdym przejściu zostaje wywołana metoda createForm, gdzie argumentem jest aktualny elem. pętli, wynik ten zapisywany jest do tablicy, całość zostaje przekazana do html.twig i tam przy odpowiednich warunkach pętlami wyświetlam formularz z danymi
druga w której rezygnuje z generatora (choć chyba wszystkie formularze powinny być przez niego tworzone):
tworzę w html.twig ręcznie pole textarea (tylko tyle potrzeba w tym wypadku) i podczas generowania widoku postów wklejam tam zawartość i wszystko ładnie podmienione
Później ten formularz zamierzam po przez AJAX wysyłać i nadpisywać edytowaną treść.

Czy możecie doradzić jak najlepiej rozwiązać ten problem (dane już są pobierane [zapytanie, które jest potrzebne do wyświetlania treści bez edycji ])?
Obecny widok html.twig (wiem, nie jestem front-end'owcem):
  1. {% extends 'base.html.twig' %}
  2. {% block title %}{% trans %}title.forum{% endtrans %}{% endblock %}
  3. {% block body %}
  4. <section class="container posts">
  5. {% set loopIndex = 0 %}
  6. {% for data in postsData %}
  7. <section class="post-box">
  8. <section class="user-data-topic col-md-2">
  9. userporfile data
  10. username: {{ data.username }}
  11. </section>
  12. <section class="col-md-10 post-section">
  13. <div class="row">
  14. <header class="col-md-12">
  15. <div class="col-md-7">
  16. nr {{ loopIndex }}
  17. </div>
  18. <div class="col-md-4">
  19. {% trans %} post.date{% endtrans %}: {{ data.0.addedDate }}
  20. </div>
  21. <div class="col-md-1">
  22. {% if userId == data.0.postAuthorId %}
  23. <a href="#" data-action="edit" data-postId="{{ data.0.id }}">{% trans %}global.edition{% endtrans %}</a>
  24. {% endif %}
  25. </div>
  26. </header>
  27. <article>
  28. {{ data.0.postText }} <br />
  29. {% if data.0.lastEditionDate != '' %}
  30. <div class="post-last-edit"> {% trans %}post.lastEdition {% endtrans %}: {{ data.0.lastEditionDate }}</div>
  31. {% endif %}
  32. </article>
  33. {% if userId == data.0.postAuthorId %}
  34. <div class="editBox " style="display:none">
  35. {{ form_start(editForm) }}
  36. <div class="form-group">
  37. {{ form_label(editForm.postText) }}
  38. {{ form_errors(editForm.postText) }}
  39. {{ form_widget(editForm.postText) }}
  40. </div>
  41. <div class="">
  42. <button type="submit" class="btn btn-primary" name="submit-edition">{% trans %}global.submit{% endtrans %}</button>
  43. <button type="submit" class="btn btn-danger" name="submit-cancel">{% trans %}global.cancel{% endtrans %}</button>
  44. </div>
  45. {{ form_end(editForm) }}
  46. </div>
  47. {% endif %}
  48. <footer>
  49. ---<br />
  50. user_desc
  51. </footer>
  52. </div>
  53. </section>
  54. </section>
  55. {% set loopIndex=loopIndex+1 %}
  56. {% endfor %}
  57. {% if userId != '' %}
  58. <section class="col-md-2">
  59. </section>
  60. <section class="col-md-10">
  61. {{ form_start(addPostForm) }}
  62. {{ form_label(addPostForm.postText) }}
  63. {{ form_errors(addPostForm.postText) }}
  64. {{ form_widget(addPostForm.postText) }}
  65. <div class="">
  66. <button type="submit" class="btn btn-primary" name="submit-addPost">{% trans %}global.submit{% endtrans %}</button>
  67. <button type="submit" class="btn btn-danger" name="submit-cancel">{% trans %}global.cancel{% endtrans %}</button>
  68. </div>
  69. {{ form_end(addPostForm) }}
  70. </section>
  71. {% endif %}
  72. </section>
  73. <script src="{{ asset('js/showTopic.js') }}"></script>
  74. {% endblock %}
destroyerr
Pierwsze pytanie: po co zmieniasz format daty? Jaka wersja Doctrine DBAL? Podałeś błąd, podaj jeszcze stacktrace. Dlaczego tak udziwniłeś to co zwraca funkcja getPostsWithTopicId? Jak wyglądają funkcje setAddedDate i setLastEditionDate? Słyszałeś o pętli foreach? Dlaczego encje nazywasz w liczbie mnogiej?

Cytat
$user = $this->getUser();
$userId = ($user !== null) ? $user->getId() : 0;
.
.
.
$user = new User;
$user->setId($userId);

To nie ma sensu. Tak samo dla Topics.
Geniesis
Już tłumaczę:
Cytat
Pierwsze pytanie: po co zmieniasz format daty?

Podejrzewam, że chodzi Ci o:
  1. for($i = 0; $i < count($postsList) ; $i++) {
  2. $postsList[$i][0]->setAddedDate($postsList[$i][0]->getAddedDate()->format('Y-m-d H:i:s'));
  3. $gettedDate = $postsList[$i][0]->getLastEditionDate();
  4. if($gettedDate != null){
  5. $postsList[$i][0]->setLastEditionDate($gettedDate->format('Y-m-d H:i:s'));
  6. }
  7. }

Powód jest banalny - chcę móc bezp. się odnieść w TWIG, jeżeli uważasz, że to bez sensu to napisz, zmienię to. (Dzięki tej pętli mogę użyć {{ data.0.addedDate }}).

Cytat
Jaka wersja Doctrine DBAL?

2.4.8 wg danych z pliku vendor/doctrine/orm/lib/doctrine/orm/version.php

Cytat
Podałeś błąd, podaj jeszcze stacktrace.

Nie jestem pewien co dokładnie znaczy "stacktrace", symfony poza owym błędem nie zwraca więcej informacji w komunikacie.
Cytat
Dlaczego tak udziwniłeś to co zwraca funkcja getPostsWithTopicId

Co rozumiesz po przez "udziwnienie"? Pobieram dane z bazy, po czym zmieniam dane w obiekcie typu datetime by była to zwyczajna data (nie obiekt) -> wyżej wytłumaczyłem dlaczego.
Cytat
Jak wyglądają funkcje setAddedDate i setLastEditionDate?

wg schematu
setAddedDate:
  1. /**
  2.   * Set addedDate
  3.   *
  4.   * @param \DateTime $addedDate
  5.   * @return Posts
  6.   */
  7. public function setAddedDate($addedDate)
  8. {
  9. $this->addedDate = $addedDate;
  10.  
  11. return $this;
  12. }

setLastEditionDate
  1. /**
  2.   * Set lastEditionDate
  3.   *
  4.   * @param \DateTime $lastEditionDate
  5.   * @return Posts
  6.   */
  7. public function setLastEditionDate($lastEditionDate)
  8. {
  9. $this->lastEditionDate = $lastEditionDate;
  10.  
  11. return $this;
  12. }

Cytat
Słyszałeś o pętli foreach

tak, prawie zawsze jej używam, nie jest to problem by zmienić to na ową pętlę zachowując te same działanie

Cytat
Dlaczego encje nazywasz w liczbie mnogiej?

Najchętniej bym napisał "bo tak", a tak poważnie, jeżeli w bazie mam tabelę z tematami to używam liczby mnogiej(wiele danych), encja odpowiada za odzwierciedlenie bazy, stąd liczba mnoga (jeżeli to błąd to napisz).

Cytat
To nie ma sensu. Tak samo dla Topics.

Dlaczego? Początkowo pobieram obecnego user'a id by wiedzieć później czy zalogowany = autor postu (w TWIG jest to potrzebne także), później owa zmienna mi nie jest już potrzebna to ją nadpisuję, tabela posts ma kolumny topic_id, post_author_id gdy chcialem najwyczajniej zrobić coś takiego: $postEnt->setPostAuthorId($userId) oraz $postEnt->setTopicId($id) to w zapytaniu wyskakiwał błąd (pola pomimo ustawienia dawały NULL), stąd też wyczytałem, że w ten sposób można rozwiązać owy problem.

Dorzucam także plik posts.orm.yml może tutaj coś zepsułem:
  1. UserBundle\Entity\Posts:
  2. type: entity
  3. table: posts
  4. repositoryClass: UserBundle\Repository\PostsRepository
  5. indexes:
  6. topic_id:
  7. columns: [ topic_id ]
  8. post_author_id:
  9. columns: [ post_author_id ]
  10. id:
  11. id:
  12. type: integer
  13. id: true
  14. generator:
  15. strategy: AUTO
  16. fields:
  17. postText:
  18. type: string
  19. length: '1000'
  20. column: post_text
  21. postAuthorId:
  22. type: integer
  23. column: post_author_id
  24. addedDate:
  25. type: datetime
  26. column: added_date
  27. lastEditionDate:
  28. type: datetime
  29. nullable: true
  30. column: last_edition_date
  31. topicId:
  32. type: integer
  33. column: topic_id
  34.  
  35. manyToOne:
  36. topic:
  37. targetEntity: Topics
  38. joinColumn:
  39. name: topic_id
  40. referencedColumnName: id
  41. postAuthor:
  42. targetEntity: User
  43. joinColumn:
  44. name: post_author_id
  45. referencedColumnName: id
  46.  
  47. lifecycleCallbacks: { }


Uczę się dopiero symfony i wiele spraw nie jest dla mnie do końca jasnych, stąd też pojawiają się kwiatki, o których nawet nie wiem, że nimi są, dlatego też pytam ludzi na forum by ich nie robić.
destroyerr
Cytat
Powód jest banalny - chcę móc bezp. się odnieść w TWIG, jeżeli uważasz, że to bez sensu to napisz, zmienię to. (Dzięki tej pętli mogę użyć {{ data.0.addedDate }}).

No to to jest całkowicie bez sensu, żeby model dopasowywać do szablonu. To widok ma sformatować datę w zależności od wymagań i twig wspiera programistów: http://twig.sensiolabs.org/doc/filters/date.html oraz http://twig.sensiolabs.org/doc/extensions/intl.html
Dodatkowo sprawia to problemy na które się napotkałeś. Pobrałeś z bazy listę postów, dokonałeś na nich zmian (właściwość addedDate powinna być typu DateTime, a jest string) i Doctrine próbuje utrwalić to w bazie danych. Dlatego próbuje wykonać metodę format na stringu.

Cytat
2.4.8 wg danych z pliku vendor/doctrine/orm/lib/doctrine/orm/version.php

Podałeś wersję ORMa nie DBAL, ale sprawdziłem sobie pasującą do tego wersję. Kolejny przykład dlaczego warto korzystać z nowych wersji, dostałbyś wyjątek wszystko mówiący o problemie.

Cytat
Nie jestem pewien co dokładnie znaczy "stacktrace", symfony poza owym błędem nie zwraca więcej informacji w komunikacie.

stacktrace/backtrace czyli ślad wywoływanych funkcji. Dziwne, bo symfony pokazuje akurat listę wywołań pod wyjątkiem.

Cytat
Co rozumiesz po przez "udziwnienie"? Pobieram dane z bazy, po czym zmieniam dane w obiekcie typu datetime by była to zwyczajna data (nie obiekt) -> wyżej wytłumaczyłem dlaczego.

Poprzez udziwnienie rozumiem zwracanie tablicy tablic z obiektem, zamiast tablicy obiektów. Zwyczajny typ dla daty to DateTime, zapisywanie daty w stringu jest udziwnieniem, które wprowadza chaos i problemy.

Cytat
Najchętniej bym napisał "bo tak", a tak poważnie, jeżeli w bazie mam tabelę z tematami to używam liczby mnogiej(wiele danych), encja odpowiada za odzwierciedlenie bazy, stąd liczba mnoga (jeżeli to błąd to napisz).

Podstawowa kwestia jest taka, że pomyliłeś ORM z innymi narzędziami. ORM nie odzwierciedla bazy danych w aplikacji, a dane aplikacji w bazie danych. Dlatego podstawą są encje do których należy dostosować tabele. Obiekt klasy Posts reprezentuje pojedynczy post, a nie żadną tabelę. Jeżeli chcesz mieć nazwy tabel w liczbie mnogiej to skonfiguruj sobie mapowanie encji, a nie zmieniaj znaczenia obiektów.

Cytat
Dlaczego? Początkowo pobieram obecnego user'a id by wiedzieć później czy zalogowany = autor postu (w TWIG jest to potrzebne także), później owa zmienna mi nie jest już potrzebna to ją nadpisuję, tabela posts ma kolumny topic_id, post_author_id gdy chcialem najwyczajniej zrobić coś takiego: $postEnt->setPostAuthorId($userId) oraz $postEnt->setTopicId($id) to w zapytaniu wyskakiwał błąd (pola pomimo ustawienia dawały NULL), stąd też wyczytałem, że w ten sposób można rozwiązać owy problem.

W aplikacji powinieneś operować na obiektach, nie na identyfikatorach. Tworzysz obiekt User, przypisujesz mu id i ustawiasz jako relację. Spójrz parę linijek wyżej i zauważ, że już masz obiekt User to po co tworzyć drugi gorszy? Jeżeli chcesz ustawić jakiś obiekt jako relację to pobierz go bazy danych, utwórz go i utrwal albo utwórz proxy tego obiektu.
Geniesis
Usunąłem pętle i zmieniłem: {{ data.0.addedDate|date("Y-m-d H:i:s") }} - oczywiście działa.
Tutaj trochę zdziwiła mnie interpretacja, ponieważ ustawiam $postEnt->setAddedDate(new \DateTime()); więc mam nadpisane jako obiekt, a interpretowało jako string (wcześniej ustawione na niego).

Zainstalowałem symfony z poziomu terminala w wersji 2.8 (3.0 ma zmienioną strukturę, więc wolałem na razie ogarnąć przy wcześniejszej, którą wcześniej trochę poznałem), pewnie stąd ta nieaktualność.

Zgadza się, normalnie pokazuje całą listę stacktrace, jednak w tym konkretnym przypadku nie pokazywało (też zdziwiło mnie to, stąd też nie wiedziałem gdzie szukać błędu).

Dzięki za rozjaśnienie odnośnie encji - już zmieniłem na l.poj.

Może to głupie ale nie wpadłem, że obiekt z danymi zalogowanego to ten sam, który później deklaruję od nowa, już to poprawilem.

Cytat
Jeżeli chcesz ustawić jakiś obiekt jako relację to pobierz go bazy danych, utwórz go i utrwal albo utwórz proxy tego obiektu.

Tutaj nie do końca jestem pewien czy dobrze Cię zrozumiałem: odkąd pamiętam tłuczono mi by nigdy nie brać więcej niżeli potrzeba (im więcej do roboty tym więcej czasu potrzeba, a wiadomo wydajność aplikacji podstawą), więc jest to w tym wypadku błędne myślenie?
Tak jak teraz (dla przykładu) mam pobieranie danych ustawione w ten sposób:
  1.  
  2.  
  3. Edit:
  4. jeżeli potrzebuję zapisać dane tylko w 1 tabeli to po co nadpisywać wszystkie inne dane z encji skoro potrzebny tylko id by móc zapisać w tej pierwszej (co innego jeżeli chcę w dwóch, wtedy wiadomo, że trzeba obie encje w pełni uzupełnić)?.
  5. $qb->select('p, u.username')
  6. ->from('UserBundle\Entity\Post', 'p')
  7. ->leftJoin('p.postAuthor', 'u', 'WITH', 'p.postAuthor=u.id' )
  8. ->addOrderBy('p.addedDate', 'DESC')
  9. ->where('p.topicId=:id')
  10. ->setParameter('id', $id)
  11. ;

i zwraca mi coś w deseń:
  1. [0] =>
  2. [0] => obiekt z danymi Post (encja)
  3. [username] => nazwa użytkownika, która jest pobierana na bazie postAuthor z encji Post
  4. ...

mam mieć:
  1. $qb->select('p, u')
  2. ->from('UserBundle\Entity\Post', 'p')
  3. ->leftJoin('p.postAuthor', 'u', 'WITH', 'p.postAuthor=u.id' )
  4. ->addOrderBy('p.addedDate', 'DESC')
  5. ->where('p.topicId=:id')
  6. ->setParameter('id', $id)
  7. ;

Zwróci:
  1. [0] =>obiekt z danymi Post (encja), jednym z elem. będzie obiekt odzwierciedlający encję User z wszystkimi danymi określonego użytkownika
  2. ...
destroyerr
Oczywiście nie należy pobierać więcej danych niż potrzeba, ale nie należy pobierać ich mniej niż potrzeba. Niestety potrzebne jest wyczucie bo nie ma żadnych reguł. Zazwyczaj więcej czasu zajmuje wybranie tylko tych wymaganych danych, łatwiej jest pobrać trochę nadmiarowo. W tej kwestii może być pomocne podział operacji modelu na dwie części Query i Command. W query można tylko pobierać dane które będą wyświetlane i tutaj można i warto optymalizować, natomiast command już powinno być w pełni obiektowe bez przedwczesnych optymalizacji.
Kolejny problem jest w tym, że jeżeli przekażesz do entity managera obiekt z relacją jako nieutrwaloną encją to powstaje problem.
Geniesis
Cytat
Kolejny problem jest w tym, że jeżeli przekażesz do entity managera obiekt z relacją jako nieutrwaloną encją to powstaje problem.

Mógłbyś tę myśl rozwinąć? Tj. co rozumiesz po przez "nieutrwaloną" encję.

Czy byłbyś tak miły doradzić mi jeszcze odnośnie drugiego pytania (generowanie formularza edycji) z pierwszego postu?
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.