Witaj Gościu! ( Zaloguj | Rejestruj )

Forum PHP.pl

 
Reply to this topicStart new topic
> Współpraca Entity
athabus
post 4.06.2018, 14:39:05
Post #1





Grupa: Zarejestrowani
Postów: 863
Pomógł: 44
Dołączył: 2.11.2005
Skąd: Poznań

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


Mam takie pytanie teoretyczne, które od czasu do czasu pojawia się w moich projektach. Najlepiej chyba będzie opisać na przykładzie.

Piszę aplikację sportową (konkretnie dla pływaków) we frameworku Symfony i w niej mam między innymi dwa obiekty Entity:
- User
- Interval

Użytkownik ma pewne cechy definiowalne - np. prędkość bazową (dajmy na to 2:00/100m), modyfikatory prędkości (np. gdy ma płetwy to płynie 10s/100m szybciej niż bez etc).

Interwał to cegiełka z której buduję treningi do wykonania. Czyli każdy interwał posiada między innymi:
- dystans do pokonania
- prędkość wyrażoną jako prdkość bazowa +/- x sekund.
- listę modyfikatorów do zastosowania - czyli np. dany interwał użytkownik ma płynąć w płetwach

I teraz dochodzimy do tego co mnie interesuje. Mam konkretny interwał w którym jest płyń z prędkością bazową - 5s oraz użyj płetw. Mam konkretnego użytkownika, dla którego prędkość bazowa to 2:00, a modyfikator płetw daje - 10s. Czyli finalnie ten konkretny użytkownik ma popłynąć ten konkretny interwał w tempie 2:00 (prędkość bazowa użytkownika) - 5s (ustawienie interwału) - 10s (bonus za płetwy) - razem daje 1:45/100m.

Pytanie brzmi - jaki obiekt powinien obliczyć tą konkretną wartość, żeby było prawidłowo i zgodnie ze sztuką.

Interwał raczej nie powinien wiedzieć o użytkowniku i vice versa. Robienie tych samych obliczeń w 50 miejscach (np. w kontrolerach) jest bez sensu i trudne w utrzymaniu. Osobiście w tym celu stosuję usługę, którą sobie nazwałem WorkoutService i mam tam takie funkcje jak np getIntevalPace.

Zastanawiam się czy to jest dobre rozwiązanie, czy może jakoś inaczej powinienem to ogarniać.

Go to the top of the page
+Quote Post
Crozin
post 4.06.2018, 18:39:46
Post #2





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

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


Cytat
Pytanie brzmi - jaki obiekt powinien obliczyć tą konkretną wartość, żeby było prawidłowo i zgodnie ze sztuką.
Nie wykorzystywać do tego w ogóle Doctrine'owskich encji, bo ich zadaniem nie jest reprezentowanie logiki aplikacji. Utwórz odpowiedni zestaw obiektów i kontraktów pomiędzy nimi do wykonania tych obliczeń/tego zadania. Ta część powinna być jak najmocniej przetestowana, bo tutaj testy są najbardziej wartościowe i faktycznie chronią przed błędami. Następnie utwórz drugi zestaw obiektów i kontraktów/relacji pomiędzy nimi do składowania tych danych w bazie (Doctrine'owskie encje). Na koniec przygotuj coś co pozwoli przenieść dane pomiędzy oboma strukturami. W każdej z tych trzech części skup się tylko na jednym zdaniu. Zazwyczaj tworzenie zewnętrznych usług do wykonywania obliczeń na niepowiązanych obiektach sprowadza się do programowania strukturalnego z wykorzystaniem obiektów/klas jako struktur transportujących dane - innymi słowy tracimy masę dobrodziejstw obiektówki.

Ten post edytował Crozin 4.06.2018, 18:40:49
Go to the top of the page
+Quote Post
athabus
post 5.06.2018, 08:51:23
Post #3





Grupa: Zarejestrowani
Postów: 863
Pomógł: 44
Dołączył: 2.11.2005
Skąd: Poznań

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


Crozin - no i właśnie tego nie rozumiem ;-) Tzn rozumiem ideę, nie wykrozystuję encji do tego etc. Tylko nie wiem jak to poprawnie rozwiązać.

Na tą chwilę dokładnie działa to u mnie tak, że mam Workout (trening) -> składający się z bloków (blocks) -> bloki składają się z zadań (task) -> zadania składają się z interwałów (interval).

Mam usługę o nazwie Workout, która odpowiada za wszelkie operacje na treningu. Czyli np. obliczanie długości trening/bloku, obliczanie temp dla danego użytkownika etc.

Mam też obiekt VCSettings w którym przechowuje ustawienia potrzebne do przekształcenia abstrakcyjnego treningu w trening dla danego użytkownika - czyli w moim przypadku obiekt ten przechowuje informacje takie jak długość basenu, tempo bazowe, modyfikatory itd itp.

Wywołania u mnie wyglądają tak

$workoutService->getIntervalPace($interval, $vcsettings);
$workoutService->getWorkoutDuration($workout, $vcsettings);
itd itp.

To jest ok, czy powinno to się odbywać jakoś inaczej. Jeśli tak to konkretnie jak? Tak jak pisze rozumiem ideę, ale nie jestem pewien jak to powinno wyglądać w praktyce.

Wg Ciebie powinienem mieć klasę typu Interval, który powiedzmy powinna mieć np. przez konstruktor wrzucone $interval(encję) + np. mój obiekt vcSettings?

Sorry, że tak drążę, ale chciałbym dobrze temat zrozumieć.

Go to the top of the page
+Quote Post
Crozin
post 6.06.2018, 06:09:46
Post #4





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

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


1. Szczerze mówiąc nie jestem dokładnie pewien czy w pełni poprawnie rozumiem problem (tak patrząc całościowo) - to na start. smile.gif Nie wiem też czy przykłady jakie podałeś wyczerpują różnorodność detali czy właściwie na tym kończą się ich wariacje. Fajnie jakbyś może pokazał nawet jakąś okrojoną wersję kodu źródłowego (szczególnie $workoutService'u) - nieraz mówi więcej niż jego opis.
2. Bardzo ważne jest to jak bardzo chcesz, czy właściwie jak bardzo potrzebujesz "rozdmuchać" obsługę tego problemu. Chodzi o to by jednak potencjalnie prostego problemu nie zacząć rozwiązywać w niepotrzebnie rozbudowany, skomplikowany i obszerny sposób (takie ang. overengineering).
3. Wstępnie wygląda to tak, że obiekty pod $interval, $vcsettings czy $workout to wyłącznie wory do przenoszenia złożonych danych (DTO), a obliczenia z nimi związane zamiast być blisko danych w postaci metod (jak to jest przewidziane w OOP) są wyniesione gdzieś zupełnie indziej w kodzie.
4. Na początek spróbuj może troszkę odwrócić relację pomiędzy obiektami. Zamiast $workoutService->getDuration($workout, $vcsettings) spróbuj do tego podejść jako $workout->computeDuration(questionmark.gif$vcsettings??, questionmark.gif$workoutService??). Być może te $vcsettings powinny być przekazane już w konstruktorze, a ten drugi argument w ogóle stanie się już niepotrzebny?
5. Ten fragment kodu (związany z samą logiką aplikacji) w miarę możliwości nie powinien mieć nawet świadomości Symfony, Doctrine'a (encji) czy innych frameworków. Dlaczego? Bo takie webowe FW nie chcą rozwiązywać konkretnych problemów, specyficznych dla Twojej aplikacji. Mają dać Ci szybką, solidną i wygodną "otoczkę" niezwiązaną bezpośrednio z celem działania samej apki (tutaj: wyliczania terningów), a związaną z całym tym "syfem" potrzebnym do tego by to gdzieś/jakoś/komuś uruchomić. tongue.gif Pozwól im skupić się na tym co faktycznie potrafią robić, samemu skupiając się mocno na swoim problemie - którego rozwiązanie już leży po Twojej stronie. smile.gif

Ten post edytował Crozin 6.06.2018, 06:15:19
Go to the top of the page
+Quote Post
SmokAnalog
post 6.06.2018, 09:02:06
Post #5





Grupa: Zarejestrowani
Postów: 1 462
Pomógł: 235
Dołączył: 3.07.2012
Skąd: Poznań

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


Warto się zastanowić czy Interwał to w ogóle encja. Czasami lepiej jest uprościć schemat danych i zamiast osobnej tabeli, trzymać pewne dane w kolumnie typu np. JSON. Wszystko zależy od tego w jaki sposób chcesz wyszukiwać i grupować te dane. Na pytanie kto powinien zajmować się obliczaniem interwałów, odpowiedziałbym że albo osobna klasa, albo klasa Interwału. Nie ma nic złego w tym, że Interwał zna szczegóły Użytkownika.
Go to the top of the page
+Quote Post
athabus
post 6.06.2018, 10:05:04
Post #6





Grupa: Zarejestrowani
Postów: 863
Pomógł: 44
Dołączył: 2.11.2005
Skąd: Poznań

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


@Crozin - kod na tą chwilę: https://www.dropbox.com/s/jn5amv1dk12pwel/w...ervice.php?dl=0

Nie wiem jednak czy zrozumiesz kod na podstawie tej jednej klasy. W każdym razie powiem tylko, że są w niej ogólnie 3 typy metod:
- podstawowe typu klonowanie poszczególnych części
- obliczanie temp/długości interwałów w zależności od ustawień vcsettings
- obliczanie generycznych temp. w Skrócie obliczenie długości treningu jest dość skomplikowane więc obliczam 3 czynniki istotne aby je cachować - tj. mnożnik metrów płyniętych w tempie bazowym, mnożnik zmienności tempa, mnożniki dla modyfikatorów. Dzięki temu potem mogę na liście łatwo podstawić te mnożnik pod danego użytkownika i obliczyć czas treningu bez zażynania basy setkami zapytań.

Co do pozostałych Twoich punktów mam pewne przemyślenia, ale na razie ich nie opisuję, żeby nie mnożyć wątków. Z częścią się zgadzam, z częścią mam wrażenie, że byłoby to dokładnie to o czym piszesz, czyli zbytnie rozbudowanie w sumie prostej rzeczy. Ale na razie nie wchodzę w szczegóły bo zależy mi bardziej na dyskusji "akademickiej" niż tym konkretnym przypadku.
Jestem ciekaw jak sam WorkutService ocenisz - mi się tam już teraz pewne rzeczy nie podobają, ale nie do końca wiem jak je sensownie poprawić.


@SmokAnalog - ale tak komunikacja user <=> interval to chyba raczej nie powinna być w encji? Na szybko wiadomo byłoby to najwygodniejsze, ale utrzymanie tego kodu to by było piekiełko potem, bo user pojawiałby się w kilkunastu klasach projektu i każda zmiana w klasie user byłaby potem koszmarem. Wydzielenie wspólnych relacji do osobnej klasy wydaje mi się sensowne. Klasy user/vcsettings już wcześniej rozbudowywałem kilka razy o nowe funkcje i świadomość tego, że mam ściśle określone punkty styku z innymi obiektami sporo upraszcza.
Go to the top of the page
+Quote Post
Crozin
post 8.06.2018, 06:11:08
Post #7





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

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


1. Przede wszystkim problemem tej klasy jest to, że musiałeś użyć aż trójelementowej listy by opisać co ona w ogóle robi. A robi zdecydowanie za dużo i powinna zostać rozbita na wyspecjalizowane obiekty.
2. Taka trochę ogólna uwaga co do tworzenia całych grafów encji, np. w createNewWorkoutBlock.
  1. $block=new WorkoutBlock();
  2. $task=new WorkoutTask();
  3.  
  4. $interval=new WorkoutInterval();
  5.  
  6. $rest=new WorkoutInterval();
  7. $rest->setType(WorkoutInterval::TYPE_REST);
  8. $rest->setDuration(10);
  9.  
  10. $task->addInterval($interval); // wylicza m. in. pozycję oraz ustawia dwu/jednostronną referencję
  11. $task->addInterval($rest); // j/w
  12. $block->addTask($task); // j/w
  13. $workout->addBlock($block); // j/w
  14.  
  15. // raczej nie powinno znajdować się to już w tej metodzie, ale jak już to wystarczy
  16. $this->em->persist($block);
  17. $this->em->flush();
  18.  
  19. return $block;
Tyle wystarczy, logika wyliczania pozycji i innych tego typu rzeczy może spokojnie zostać zamknięta w encji nadrzędnej.
3. Trochę analogicznie jest z klonowaniem. Też przeniósłbym to do metod w ramach encji. Swoją drogą uważaj przy korzystaniu z clone w przypadku encji. Można z tego korzystać, ale niesie to za sobą kilka implikacji (patrz: dokumentacja). Zdecydowanie polecałbym Ci zrobić całość klonowania ręcznie. Zdecydowanie upraszcza i bardziej klarowne staje się wtedy to czy robimy płytką czy głęboką kopię.
4. Encje traktuj tylko jako coś co wykorzystywane jest niemal wyłącznie w komunikacji z bazą danych, nie jako fundament obliczeń dla całej apki (niestety takie podejście jest bardzo popularne w sieci/dokumentacji symfony).
5. Uwzględniając powyższy punkt oraz to co widać w kodzie, wydaje się, że tutaj encje są fajnie zaprojektowane, ale dalsze wyliczenia dla faktycznej logiki aplikacji nie powinny już być wykonywane na nich, ale na wyspecjalizowanych obiektach (nowych klas) z części nazwijmy to domenowej aplikacji. Jaka będzie różnica pomiędzy tymi dwoma zestawami obiektów? Przykładowo te z części domenowej w ogóle nie potrzebują żadnej wiedzy na temat jakiegoś użytkownika, który widzę, że się tam przewija. Nie potrzebują też żadnych właściwości z serii itemOrder - potrzebują, by kolekcje podrzędnych obiektów były po prostu uporządkowane (co PHP-owe tablice zapewniają).
5. getWorkoutDistance - te trzy zagnieżdżone foreache powinny być raczej zastąpione jednym, gdzie źródłem danych będzie jakiś iterator. Co lepsze taki iterator mógłby od razu w momencie swojego tworzenia mieć przekazane jakiego typu interwałów w ogóle (nie)chcemy z niego uzyskać (3 zagnieżdżone foreache z ifem zmieniają się w jednego foreacha). A co jest jeszcze lepsze? Ta metoda w ogóle może być spokojnie przeniesiona do nowej klasy WorkoutZCzęściDomenowej.
6. W angielskim nie ma słówka "brutto". :-P
7. Analogicznie jak z getWorkoutDistance wydaje się, że getEstimatedWorkoutDuration, getWorkoutDuration, getBlockDuration czy getTaskDuration można przenieść do wybranych klas z części domenowej.
8. Te wszystkie duration nie wyrażałbym w intach czy floatach, a w DateInterval. Zdecydowanie lepiej będzie nadawać się do reprezentowania takiej wartości - chociażby ze względu na obsługę jednostek, co wydaje się bezwzględnie konieczne tutaj.
9. Te wszystkie metody z vcsettingsami też wydają się do przeniesienia.
10. Unikaj rzucania wyjątków klasy Exception - użyj bardziej wyspecjalizowanej, a jeżeli takie nie ma (która pasowałaby do danej sytuacji) stwórz swoją.
11. Mam wrażenie, że do wyliczania wielu z tych interwałów/modyfikatorów/temp i innych świetnie mogłoby nadać się podejście bazujące na wzorcu wizytatora (ang. visitor pattern). Tylko z tym wstrzymałbym się na początku, bo kto wie czy nie jest to już właśnie to zbytnie rozdmuchanie o którym wcześniej pisaliśmy.

I tak chyba doszedłem do końca tej klasy, co do której wydaje się, że... może ona spokojnie być kompletnie zaorana! tongue.gif Wtedy też problem z pierwszego punktu sam się rozwiąże.

Ten post edytował Crozin 8.06.2018, 06:16:56
Go to the top of the page
+Quote Post
athabus
post 10.06.2018, 15:11:51
Post #8





Grupa: Zarejestrowani
Postów: 863
Pomógł: 44
Dołączył: 2.11.2005
Skąd: Poznań

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


Crozin tak jak już pisałem Ci na PW, jeszcze raz dzięki za tak rozbudowaną odpowiedź.

Skłoniła mnie ona do przeanalizowania tematu i trochę poczytałem... Szczerze to mam teraz mały maindfuck w głowie.

Zanim zacznę wchodzić w szczegóły tematu to chyba trzeba zacząć od ogółu. Otóż moje podejście do ww klasy chyba wynika z ogólnie błędnego paradygmatu na którym się oparłem. Gdzieś tam w trakcie nauki Symfony natrafiłem na materiały, które być może trochę opacznie zrozumiałem, ale ogólnie zrozumiałem z nich tyle:
- klasy entity to prosta warstwa dostępu do danych, która powinna w zasadzie w 95% mieć gettery/settery + 5% jakiejś bardzo prostej logiki obiektu.
- cała logika biznesowa powinna trafić do bezstanowych klas serwisowych.

Po dyskusji w tym poście zacząłem temat drążyć i faktycznie jest takie podejście - nie jest to mój wymysł (uff) i nazywa się to klasami anemicznymi. Wiele osób jednak krytykuje to podejście jako zaprzeczenie idei obiektywności. I tak zacząłem sobie to przemyśliwać - faktycznie klasie serwisowej bliżej do programowania strukturalnego niż obiektowego. Przy relatywnie małej aplikacji, takiej jak moja jest to nawet dość wygodne podejście - cała logika biznesowa znajduje się w kilku klasach serwisowych - jest to dość łatwe do poprawiania, rozwijania i nie tworzy dużej ilości kodu - jednym słowem nie jest to do końca głupie podejście...

Z drugiej strony jednak faktycznie jest to zaprzeczenie obiektowości - w tym podejściu obiekty są tak na prawdę strukturami danych a nie obiektami.

Ogólnie więc rozumiem w czym jest problem, ale nie wiem jak w praktyce to poprawić. Trochę zaczyna mi się mieszać co powinno być bezstanowym serwisem, a co powinno być rolą obiektu. Czy moje Entity powinny zostać "anemiczną strukturą danych" i powinna pojawić się jeszcze jedna warstwa, czy powinienem dodać logikę do samych klas entity. Załóżmy, że tworzę nową warstwę - to by oznaczało, że każdy istotny obiekt entity powinienem powielić? Przykładowo w moim przypadku powinienem stworzyć kolejną klasę Workout, która w konstruktorze przyjmowała by Entity Workout + serwisy, które są potrzebne do logiki biznesowej?

Przykładowo czy taki obiekt powinien móc sam się zapisać (czyli mieć dostęp do EntityMenagera w Symfony)?

W innej części aplikacji mam np. bardziej rozbudowany serwis służący do generowania komend głosowych - korzysta on z serwisu zewnętrznego do generowania komend głosowych, z serwisu dokonującego tłumaczeń itp. Czy teraz obiekt workout powinien także takie rzeczy robić jak generować pliki dźwiękowe?

Ogólnie mam spory problem aby ogarnąć podział obowiązków w praktycznym przykładzie. Czytając trywialne przykłady omawiające różne wzorce projektowe, gdzie klasy są na 5 linijek, a przykłady mocno uproszczone wszystko wydaje się oczywiste - ale jak trafia na normalną aplikację, to to wszystko przestaje być już oczywiste.

Chętnie bym rozrysował graf zależności w mojej aplikacji jeśli znalazłby się ktoś, kto by poradził jak to wszystko dobrze poukładać i rozrysował jak krowie na rowie, jak to powinno być. Wiem, że nie ma jednego dobrego rozwiązania, ale chętnie bym poznał różnej podejścia. Znalazłby się ktoś chętny do dyskusji w takim temacie?
Go to the top of the page
+Quote Post
Crozin
post 11.06.2018, 10:05:18
Post #9





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

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


1. Nie bój się tworzyć wielu dedykowanych klas do rozwiązywania wielu różnych problemów w aplikacji. Unikaj raczej tworzenia "obiektu pod wszystkie możliwie zadania", bo to się nie sprawdza.
2. Staraj się by raczej detal implementacji warstwy zapisu danych (tutaj: baza danych + doctrine) nie wyciekał do innych warstw aplikacji. Innymi słowy obiekty domenowe same w sobie raczej nie powinny - bo i po co - mieć wiedzy n/t Doctrine'a.
Cytat
W innej części aplikacji mam np. bardziej rozbudowany serwis służący do generowania komend głosowych - korzysta on z serwisu zewnętrznego do generowania komend głosowych, z serwisu dokonującego tłumaczeń itp. Czy teraz obiekt workout powinien także takie rzeczy robić jak generować pliki dźwiękowe?
Tutaj musiałbyś lepiej przybliżyć sytuację.
Cytat
Czytając trywialne przykłady omawiające różne wzorce projektowe, gdzie klasy są na 5 linijek, a przykłady mocno uproszczone wszystko wydaje się oczywiste - ale jak trafia na normalną aplikację, to to wszystko przestaje być już oczywiste.
Dużą sztuką jest doprowadzić do tego by skomplikowany problem przedstawić w ujęciu trywialnego kodu. To jest jedna z trudniejszych umiejętności do nabycia.

IMHO zacznij sobie te problemy rozwiązywać po kolei, szybko prototypując. Pomiń na chwilę warstwę zapisu danych do bazy, skup się na problemie. Jak to będzie zrobione będziesz mógł przejść dalej, tj. do zapisu.
Go to the top of the page
+Quote Post
athabus
post 13.06.2018, 16:49:24
Post #10





Grupa: Zarejestrowani
Postów: 863
Pomógł: 44
Dołączył: 2.11.2005
Skąd: Poznań

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


Ok poczytałem trochę i mniej więcej mam już wizję tego jak mógłbym opisać strukturę swoich obiektów. Zaczynam już rozumieć ideę kompozycji i wydaje mi się, że powinienem skorzystać z wzorca strategii. Widzę już oczyma wyobraźni piękną strukturę z klasą Interval z której dziedziczą poszczególne typy interwałów typu FixedTimeInterval, TempoInterval itd. Oczyma wyobraźni projektuje już interfejsy do poszczególnych cech interwałów typu DurationInterface z którego powstają rzeczywiste klasy zachowań zwracające długość trwania danego interwału w zależności od parametrów danej implementacji interwału...

Jednego tylko kurde nie mogę rozkminić - jak to wszytko ująć w ramach Symfony, tj jak później taki obiekt zapisać do bazy, jak utworzyć na podstawie przetworzonego formularza etc. Wszystko jest dość mocno powiązane z Entity i jakby filozofia frameworka jest na tym oparta. Te klasy typu FixedTimeInterval powinny dziedziczyć z Entity, żeby przy okazji być samym Entity?

Tak jak już (wydaje mi się) rozumiem duży obraz, tak chyba bez fragmentu kodu nie pojmuję jak to zaimplementować w Symfony. Pewnie jest jakieś eleganckie i proste rozwiązanie, ale mi to umyka...

Bezstanowe serwisy są tu dość prostą koncepcją dodającą po prostu zachowania do Entity i ich relacje... Nie mam pojęcia jak przejść z takiego modelu na to o czym piszesz Ty ;-( Znasz może jakiś prosty projekt OS gdzie mógłbym to o czym piszesz zobaczyć?
Go to the top of the page
+Quote Post
Crozin
post 15.06.2018, 07:52:49
Post #11





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

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


Cytat
Jednego tylko kurde nie mogę rozkminić - jak to wszytko ująć w ramach Symfony, tj jak później taki obiekt zapisać do bazy, jak utworzyć na podstawie przetworzonego formularza etc. Wszystko jest dość mocno powiązane z Entity i jakby filozofia frameworka jest na tym oparta. Te klasy typu FixedTimeInterval powinny dziedziczyć z Entity, żeby przy okazji być samym Entity?
Nie musisz, ani nawet nie powinieneś tego ujmować w ramach framworka. Jeżeli z jakiegoś powodu FW by Cię do tego zmuszał byłby to słaby FW - Symfony na szczęście taki nie jest, chociaż nieraz po dokumentacjach mocno sugerują takie użycie. Ten fragment kodu raczej się nie powinien przejmować żadnymi encjami czy innymi formularzami, bo jest od nich niezależny. Jeżeli potrzebujesz formularza, utwórz dla niego dedykowaną klasę reprezentującą formularz - nie korzystaj z obiektów domenowych czy innych encji tutaj. Analogicznie z encjami. Klasy z różnych części aplikacji (część domenowa, część dostępu do źródła danych - encje, część związana z obsługą formularzy) nie powinny między sobą dziedziczyć.

Jeśli masz już jakiś fragment zrealizowany możesz go tutaj pokazać i później zacząć się zastanawiać nad następnymi problemami. smile.gif
Go to the top of the page
+Quote Post
athabus
post 15.06.2018, 08:24:10
Post #12





Grupa: Zarejestrowani
Postów: 863
Pomógł: 44
Dołączył: 2.11.2005
Skąd: Poznań

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


Crozin nie mam żadnego fragmentu kodu, bo kompletnie nie wiem jak się do tego zabrać. Niestety tak jak piszesz wszelkie "best practices" czy "demo project" w Symfony są tworzone tak jak ja to robię, czyli Entyty + Serwisy ;(

Sprowadzając to do najbardziej uproszczonego przypadku.

Mam Entity Interval + tworzę obiekt domenowy Interval (docelowo interwał byłby klasą abstrakcyjną po której dziedziczyłyby różne typy interwałów, ale tutaj załóżmy, że jest to od razu konkretna klasa).
Pytania jakie mnie nachodzą:
- obiekt interwał ma jakieś properties, w dużej mierze takie same jak samo Entity. Czy zatem należy stworzyć konstruktor, który "przepisze" te wartości z entity, czy po prostu Entity dać jako element Interwału?

- załóżmy, że wykonałem jakieś operacje na Interval, które chce teraz zapisać do bazy. Znów - w którym miejscu kodu przekazać moje zmiany do Entity?

Takich pytań mam więcej - np. jak obsługiwać relacje etc. Bo teraz jedyne rozwiązania jakie mi przychodzą do głowy to ogromna redundancja kodu aby w obiekcie Interval odtworzyć sporo zachowań, które w Entity (dzięki Doctrine) mam z automatu.

No po prostu utknąłem w takim punkcie, że nie wiem jak zacząć i z chęcią zobaczył bym jakiś projekt w Symfony, gdzie jest to rozwiązane prawidłowo, żeby zaskoczył. Przypuszczam, że po prostu blokuję się w jakimś miejscu na złym rozwiązaniu.
Go to the top of the page
+Quote Post
Crozin
post 16.06.2018, 11:58:34
Post #13





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

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


Wydaje mi się, że za bardzo skupiasz się w tej chwili na Symfony/Doctrine/innych bibliotekach oraz na problemach których jeszcze nie masz tworząc z relatywnie prostego problemu rozdmuchanego potwora, do którego faktycznie trudno podejść. Wcześniej pisałeś o tym jak już oczami wyobraźni widzisz strukturę i kontrakty pomiędzy obiektami, teraz piszesz o nieistotnych z punktu widzenia tego problemu bzdetach. smile.gif Zignoruj na chwilę problem obsługi bazy danych czy ogólnie ich składowania. Olej problem wprowadzania danych czyli formularze. Wypnij cztery litery na tworzenie serwisów w kontenerze zależności zrób to manualnie na start.

W skrócie: utwórz sobie prosty, śmieciowy kontroler z jedną akcją jako punkt wejścia do kodu, wpisz do niego ręcznie jakiś stan początkowy dla danych, uruchom kod który ma te dane przetworzyć i na koniec nawet najprostszym Symfonowym dump() wyświetl rezultat by sprawdzić czy otrzymane dane są prawidłowe.
  1. class DummyController {
  2. public function runAction(): Response {
  3. // Dane początkowe
  4. $staminaCalculator = new JakasDodatkowaUsluga();
  5.  
  6. $intervalA = new AbcInterval(new DateInterval('PT5M'));
  7. $intervalB = new RestInterval(new DateInterval('PT1M'));
  8. $intervalC = ...;
  9.  
  10. $athlete = new Athlete(endurance = 5, stamina = 10);
  11.  
  12. $blocks = ....
  13. $vcsettings = ...
  14. $workout = new Workout($blocks, $vcsettings)
  15.  
  16. // uruchom istotny kod, który w końcu coś robi
  17. $result = $workout->calculateDuration($athlete, $staminaCalculator); // przykladowo coś takiego
  18.  
  19. // wyświetl sobie dane by zweryfikować czy calculateDuration() działa prawidłowo
  20. dump($result);
  21.  
  22. die('a olać co tam by się chciało dalej dziać - nie ma znaczenia teraz');
  23. }
  24. }

Utwórz sobie kilka takich akcji, które pozwolą Ci zweryfikować czy istotny kod Twojej aplikacji działa poprawnie. Jak ten problem będziesz miał rozwiązany będzie można przejść do tematu zapisu jakiś danych do bazy - ale wtedy zobaczysz, że nie będziesz już musiał sobie w ogóle zaprzątać głowy obliczeniami i kod znowu będzie prosty. Później rozwiążesz sobie problem wydobycia danych z bazy czy np. z formularzy - dwa kolejne problemy do osobnego rozpatrzenia. I tak dalej i tak dalej. W tych późniejszych zadaniach Symfony/Doctrine mocno ułatwią Ci pracę, bo te problemy adresują i potrafią rozwiązywać - ale to później. "best practices" mają zastosowanie dopiero w momencie korzystania z elementów Symfony/Doctrine - ale to zaś: później. Pamiętaj też, że nad nimi powinny stać "best practices" pisania kodu samego w sobie, a to już jest mocno niezależne od bibliotek, frameworków czy nawet języków.

PS. Jeśli zamienisz sobie śmieciowy kontroler na klasę testu jednostkowego, a dump() na serię assert() będziesz miał książkowy przykład TDD w stylu AAA. :-)
Go to the top of the page
+Quote Post

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 Wersja Lo-Fi Aktualny czas: 19.06.2018 - 13:42