Na początek po reorganizacji forum chcielibyśmy zaproponować wam temat dotyczący mapowania tabel z baz danych na obiekty w PHP.
czy czegos podobnego nie robi http://propel.phpdb.org ? Bardzo ciekawe rozwiazanie. Niedawno znalazlem i zachwycam sie co jakis czas Poza łatwością i przyjemnością w pisaniu, nie miałem okazji przetestować wszystkiego pod względem wydajności.
Edit:
Chociaz przy propel'u to by sie przydalo narzedzie do tworzenia tablic w XML'u. Coś podobnego w stylu tworzenia tablic w phpMyAdmin. Bo na większą skale jest to kosmar tak pisac
Moje doświadczenia z klasami tego typu są dość małe. Zastanawia mnie jednak kwestia wydajności. Dla przykładu ZF ma klasę Zend_Db_Table, która jest implementacja Active Record. Zauważyłem, że już przy inicjacji takiej klasy wykonywane jest jedno dodatkowe zapytanie -> w tym przypadku Describe Table. Czyli np, aby odczytać wiersz o id=5 muszę wykonać 2 zapytania - 1 opisujące strukturę tabeli, drugie odczytujące wiersz. Powiem szczerze, że trochę mnie to zniechęca do tego rozwiązania - stosuje je tylko w panelu administracyjnym, gdzie mogę sobie pozwolić na wykonywanie dodatkowych zapytań - na stronach, które są "mocniej oblegane" raczej wolę sam optymalizować zapytania.
Druga sprawa to obiekty oparte o ActiveRecord stoją trochę w sprzeczności z ideą OOP i dają publiczny dostęp do wszystkich swoich składowych. Sam np. nadpisałem Klasę Zend_Db_Record tak aby była możliwość określenia parametrów publicznych i prywatnych oraz zakresu wartości jakie przyjmują.
Trzecia sprawa to to, że początkowy zysk w postaci szybszego kodowania jest opłacony przez mniejszą przejrzystość kodu w przyszłości.
Z drugiej jednak strony muszę przyznać, że jest to kolosalne ułatwienie przy pisaniu i praca idzie znacznie szybciej - nie trzeba "klepać" ręcznie każdego małego obiektu.
Nie znam framework'a zend'a ale propel dzieli sie na dwie czesci, ta ktora generuje klasy dla bazy danych i druga gdzie wykozystujesz juz gotowe obiekty, ktore nie za bardzo sa sprzeczne z OOP, raczej idealnie wpasowywują się w założenia OOP.
Osobiscie znam jedynie propela, jeśli chodzi o tego typu skrypty. Czy jest ktoś w stanie podać jakieś inne z którymi miał jakąś styczność ?
I mamy użytkownika o identyfikatorze 5. Całościowo w jednym zapytaniu.
<?php $user = UserPeer::retrieveByPK(5); ?>
Moim zdaniem przejrzystość idzie tylko na plus.
<?php $author = new Author(); $author->setFirstName("Jack"); $author->setLastName("London"); $author->save(); ?>
Propela jeszcze nie próbowałem, ale z przykładów, które podajesz widzę, że moje przekonania są błędne. Niestety w ZF nie działa to, aż tak fajnie i trochę mnie to rozwiązanie zniechęcało.
Co do przejrzystości kodu to znowu -> jeśli tak jest jak piszesz, to zmienia postać rzeczy. W ZF tworzysz klasę dziedziczącą po Zend_Db_Table i ona nie definiuje ani pól ani ich typów. Później piszesz np.
<?php $author->setField('name', 'Szymon'); ?>
I mamy użytkownika o identyfikatorze 5. Całościowo w jednym zapytaniu.
<?php $user = UserPeer::retrieveByPK(5); ?>
A ja sobie napisałem swoją klasę (swego czasu opisywaną w phpsolutions) i to z niej korzystam. Zestaw Finderów wraz z obiektami (które wszystkie są dokładnie takie jak mi się to wymarzy, nic nie narzuca mi propel) to wg mnie najlepsza sprawa na jaką mogłem wpaść.
Ja mam podobnie (tak mi sie wydaje) jak ActivePlayer. Własna klasa, nawet nie wiem jak dziają te inne zendowe czy propelowe
Klasa bazowa, która zawiera operacje zapisu i odczytu tabeli oraz zawiera wspolne pole dla wszystkich tabel (akurat u mnie w projektach tak mam).
Nastepnie dla danej tabeli tworze klase, ktora dziedziczy po bazowej i zawiera definicje dodatkowych pol tabeli, ktorych nie ma w bazowej. I to wszystko.
Jesli podczas zapisywywania danej tabeli potrzebuje wykonac jakies dodatkowe operacje, to nadpisuje metode save z bazowej i robie tam co mi potrzeba.
Rozwiązanie jest banalnie proste, elastyczne i przejrzyste. Jak musze cos kombinowa przy tabeli, to robie to tylko w danej klasie tabeli, nie latam juz po zadnych innych czesciach aplikacji.
No to robisz prawie tak samo jak propel, Tylko ze propel generuje ci wszystko na podstawie xml'a. Masz obiekty dla tablic, masz plik sql zeby zrobic insert do bazy. Osobiście zapoznaje się jedynie z propelem, bo zend jakos mi nie podchodzi. I musze powiedziec ze trzeba sie troche przedstawic zeby wykozystac w pełni możliwości.
Jedyne co mnie martwi to wydajność której chyba na razie nikt nie sprawdził, jak bardzo tracimy na wydajności. Bo wygoda to jedno a wydajnosc to drugie. z czego chyba bardziej by mi zalezalo na tym drugim.
Pragnę nadmienić, że w przypadku Propela nie trzeba każdorazowo powoływać do życia instancji obiektu. W chwili gdy mamy do czynienia z odpytaniem na rzecz tabeli możemy wykorzystać obiekt Criteria i przy jego pomocy wymodelować zapytanie delete. Troszkę problematyczny jest update. W przypadku wstawiania danych zapytanie jest wykonywane dopiero po wykonaniu metody save(), a więc, bez zbędnych dodatków.
Może się wyda dziwne pytanie, ale czy obiekt nie może wyciągać danych z bazy, tylko kiedy są mu potrzebne a ich nie ma? Czyli nie podczas tworzenia a podczas odczytu (podkreślam kiedy ich nie ma).
Eeeee...
@Sedziwoj czy mi się zdaje czy mówisz o leniwej konkretyzacji??
Ja piszę to co myślę, tak kochane 'wzorce projektowania' czy inne aspekty jak się okazało mi są obce, jak również zbędne, da się samemu wymyślić.
A chodzi mi o to co napisałem, chyba wyraziłem się jasno?
Że pobieramy dane nie przy tworzeniu obiektu, a przy wybieraniu nich.
A jako że nie mam zbytniego doświadczenia, pytam się o słuszność takiego postępowania, bo ma ono pewne zalety ale też wady. Tylko że ja mogę wszystkich nie widzieć (patrz brak doświadczenia) więc pytam, a że nie było to poruszane uważam, że jest na miejscu. (nie podaję tu swoich wywodów, bo oczekuję ich weryfikacji, ponieważ mogą okazać się moimi wymysłami tylko)
<?php // pobieramy obiekt, Propel wykona zapytanie: // SELECT FROM authors WHERE author_id = 11 $author = AuthorsPeer::retrieveByPK(11); while($book = $author->getBooks()) { // przegladamy ksiazki, tutaj jest wykonywane zapytanie: // SELECT FROM books WHERE author_id = 11 http://www.php.net/echo $book->getTitle() .'<br />'; } ?>
@splatch - nie do konca dobry przyklad
pobierajac w propelu wiersz zwracany jest obiekt. problem w tym ze czesto chcesz pobrac np. tylko tytuly stron bez ich tresci, a samą treść tylko przy jej wyświetleniu w akcji show (która dajmy na to jest jeszcze w aplikacji cachowana). ustawiając w propelu lazy_load dla kolumny content w tabeli pages wartosc content pobierana jest tylko gdy wykonasz getContent() na obiekcie a nie zawsze gdy pobierasz dowolna ilosc obiektow pages.
jesli sie myle niech ktos mnie poprawi
Mnie przed ORMami odstrasza jedna rzecz.
Jeżeli mam powiedzmy użytkownika, który należy do jakiejś grupy.
I teraz chce dostać jego imię, nazwisko i nazwę grupy do której należy to w przybliżeniu musiałbym zrobić coś takiego:
$user = new User($id);
$group = $user->getGroup();
Co powoduje wykonanie dwóch selectów zamiast jednego używającego złączenia tabel.
Poza tym wszędzie gdzie czytam o ORMach to zawsze podawany jest przykład taki jak powyżej, który ma być uzasadnieniem tego, że to jest fajne.
Ale co w sytuacjach kiedy potrzebuję mieć zapytanie z kilkoma złączeniami, limitem, sortowaniem, funkcjami specjalnymi typu CONCAT, funkcjami agregującymi itp? Mam wtedy dla takiego zapytania zrobić perspektywę i użyć wzorca Active Record do opisania tej perspektywy? Niby tak można ale wtedy w bazie będę miał 10 razy więcej perspektyw niż tabel...
Nie wiem jak to jest w innych ORM'ach ale właśnie zaczynam uczyć się propela i tam jest to dosyć ciekawie rozwiązane.
Z miejsca masz wygenerowane metody, służące do robienia joinow po foreign key. Możesz zarówno pobrać tylko jedną wybraną tablę, złączyć ją z jakąś tabelą, albo złączyć ją ze wszystkimi możliwymi tabelami.
Poza tym zawsze zostaje ci samemu odczytać odpowiednie dane wywołując odpowiednie zapytanie sql i użyć metody populateObject, która służy do wypełnienia obiektów ręcznie pobranymi danymi.
Ogólnie ORM'y (na przykładzie propela) mają swoje wady - chociażby dość duża ilośc kodu do przeparsowania (klasy propela są dość sporawe), ale jeśli chodzi o optymalność zapytań sql to zawsze idze je jakoś podrasować. Trzeba tylko sobie zdawać sprawę, że mechanizmy takie jak propel są po to, aby ułatwiać standardowe zadania i proste operacje - jeśli chodzi o te bardziej złożone to nikt tego za programistę nie zrobi.
Do niedawna miałem podobne zdanie do Ciebie, ale teraz gdy poznaje propela (dzięki właśnie temu tematowi) to zmieniam poglądy. Propel zdjął ze mnie konieczność pisania banalnego kodu do operacji CRUD i mogę się bardziej skupić nad logiką aplikacji jako całości. Jeśli chodzi o wydajność - to zgodzę się, że ORM nigdy nie będzie tak wydajny jak własne klasy - ale nie są to różnice na tyle duże aby sobie nimi zaprzątać głowę - co z tego, że aplikacja będzie chodziła 0,05s wolniej, jeśli dzięki temu zamiast pisać ją przez dwa tygodnie, zrobię to w 4-5 dni.
Nie, chocby w Railsach masz :include => 'group' i wykona ci zapytanie z JOINEm do grup przy znajdywaniu usera (User::find($id)). Dodatkowo dodajesz ORDER i LIMITY. Po SQLu tez da sie znalezc obiekty
Yacho nie dość, że cytujesz moją wypowiedź sprzed 2 miesięcy, to jeszcze była ona na temat AR a nie ORM
Tak jak tam pisałem odnosiłem się do AR na przykładzie ZF, z którym miałem okazję spędzić trochę czasu. Problem jak się tu pojawił to, to że w momencie zmiany nazwy pola w tabeli musiałbym poprawić wszystkie odwołania, które korzystały z tej tabeli - także akurat Twój przykład tutaj nie za bardzo był trafny.
Pod wpływem tego wątku zainteresowałem się ORM (konkretnie Propelem) o czym też zresztą pisałem już w tym wątku Powiem szczerze, że jestem pozytywnie zaskoczony tym rozwiązaniem - nie myślałem, że w PHP uda się takie coś popełnić. Właśnie kończę pierwszy projekt z wykorzystaniem Propel'a i to jest coś czego mi od dawna brakowało.
Co do ORM to nie podoba mi się przekonanie, że są one bez sensu bo przy skomplikowanych zapytaniach wykonują nieoptymalne operacje i są be. Wiadomo, że jeśli robisz joina po wielu tabelach, czy wykonujesz jakieś skomplikowane zapytanie, to automatix nie jest najlepszym wyjściem - ORM ułatwia korzystanie z bazy, ale nie zdejmuje z programisty konieczności myślenie i optymalizacji. Po prostu są rzeczy, które można zostawić Propelowi do zrobienia i są takie w których trzeba mu trochę pomóc.
W propelu podoba mi się to, że przewidziano możliwość własnego konstruowania zapytań czy dodawania logiki do klasy. Programowanie z Propelem to jest po prostu bajka.
Myślę sobie teraz o ORM...
Według mnie jest to utrudnianie sobie życia. Chyba lepiej poznać SQL niż uczyć się metod, które działają tak samo jak polecenia w SQL-u... Argument? Mamy jakieś złożone zapytanie, przy pomocy takiego ActiveRecords tego nie zrobimy. No i co? Musimy się uczyć SQL-a lub szukać po internecie jak coś zrobić. A nie lepiej od razu poznać go? Aplikacja działa szybciej, bo nie odpala iluś tam metod zanim wykona zapytanie.
IMHO ORM jest ułatwieniem dla początkujących
Jestes w błedzie Misiu (a moze pingwinku;) )
Złozone zapytania? Ich raczej czesto nie piszemy.
A co powiesz na to, że piszesz jakąś super złożoną aplikację. W bazie trudno się połapać a złożone zapytania to są co drugą linijkę w kodzie.
Nie zrobisz tego przy użyciu, np. activerecords. Co prawda masz tam metodę query(), ale jak jest activerecords to po co taka metoda? Skoro: złożone zapytania => "Ich raczej czesto nie piszemy."...
Według mnie lepiej korzystać np. z PDO
// I jeszcze jeden argument:
Przez ORM kod aplikacji się wydłuża
<?php $news = NewsPeer::doSelect(new Criteria()); ?>
No to zaczynamy:
Sam AR moze i jest trudniejszy i bez sensu przy PDO, ale juz odpowiednio zbudowany ORM to juz inna bajka. Przyklady beda sie opierac na moim skrypcie. Dodatko do pelni szczescia potrzebne jest np. PHPIDE aby ladnie podpowiadalo metody obiektu Zacznijmy od tego ze strukture bazy danych definiujemy podobnie jak w propelu w xmlu. Na podstawie tego sa generowane odpowiednie klasy modelu. Najpierw "tworzymy" zapytanie
<?php $oNews = new NewsModel(); $oNews->addWhere( NewsModel::CAT_ID, swRequest::Get( 'id' ) ); $oNews->addJoin( NewsModel::AUTHOR_ID, UsersModel::ID, 'LEFT' ); $oNews->addJoin( NewsModel::CAT_ID, News_categoriesModel::ID, 'LEFT' ); $aNews = $oNews->selectJoin(); ?>
No i znowu tutaj kazda metoda odpowiada kolumnie w bazie danych. Dodatkowo mamy tutaj filtrowanie danych, tak wiec nie wstawimy jakiegos stringa do kolumny ktora oczekuja liczbe. I powiedz mi ze to jest niewygodne to cie normalnie nie wiem co
<?php $oNews = new NewsModel(); $oNews->setAuthor_id( 1 ); $oNews->setCat_id( swRequest::Post( 'news_categories_id' ) ); $oNews->setContent( swRequest::Post( 'content' ) ); $oNews->setCREATED_AT( http://www.php.net/time() ); $oNews->setTitle( swRequest::Post( 'title' ) ); $oNews->insert(); ?>
Dla mnie to zawsze będzie niewygnodne, co z tego, że podpowiada mi składnie...
Więcej czasu zabiera wykonywanie zapytań
Co z tego? Np. to ze nie musisz pamietac nazw wszystkich tabel ani kolumn w bazie danych. To ze masz wieksza kontrole. To ze jest wygdniej Dorosniesz to zrozumiesz (joke)
Witam
Ot dorzuce swoje 3 grosze.... Osobiście używam PEAR DB_DataObject... Bardzo mnie sie podoba mialem troch problemów tyczącyc sie pierwszegoo uruchomienia i pierwszego utworzenia klas odwzorowujacych tabele.... Ale teraz wszystko idzie gładko..... Wszystko tworzy sie automatycznie pozniej tylko wykorzystuje klasy... Do tej pory nie zauważylem nadmiaru zapytań chciaż czasem jestem zbyt wyrozumiały wobec ilośici zapytań generowanych przez moj kod... Wczećniej uzywałem ADOdb i teraz wydaje mnie sie ze teraz ograniczyłem liczbe zapytań.... Kod też staje sie bardziej przejżysty ot chodźby pobranie usera:
<?php //$id = primary key; $user = DB_DataObject::factory('users_desc'); $user->get($id); ?>
<?php $user = DB_DataObject::factory('users_desc'); $user->id = $id; $user->online = 1; $user->find(); ?>
~Sokal a dlaczego uważasz że ORM jest dla osób nie znających SQL'a?
Tak się składa że jest dokładnie odwrotnie. Nie podejdziesz do żadnego ORM'a nie mając solidnych podstaw i wiedzy o SQL.
"IMHO ORM jest ułatwieniem dla początkujących " - to Twoja opinia. A praktyka pokazuje że żaden większy bądź duży projekt nie jest pisany bez ORM'a
Już pomijam PHP, w Java na przykład większość aplikacji śmiga na http://www.hibernate.org.
splatch ty jakiś agresywny ostatnio jesteś
Sokal - ORM nie jest po to aby nie uczyć się SQL. Ideą (tak mi się przynajmniej wydaje) to zdjęcie z programisty powtarzania ciągle tych samych operacji na bazie danych. Ciagłego pisania banalnych selectów itd. W PHP w porównaniu z innymi językami duży procent rzeczy, które robisz są "banalne", dlatego, że nie ma narzędzi automatyzujących pracę, albo mało osób z nich korzysta.
Powiem szczerze, że czasami jak pisze jakąś prostą stronkę to już mi się chce za przeproszeniem rzygać jak muszę napisać kolejny obiekt DAO, który w zasadzie robi to samo co inne tylko ma trochę inne nazwy pól itp. Dzięki Propelowi, frameworkom itd itp możesz skupić się na tych bardzie zawiłych rzeczach, a te rutynowe zostawiasz odpowiedniemu narzędziu.
Co do tego, że ORM jest dla osób nie znających SQL to mam takie zdanie jak Mike - żeby napisać dobrze coś w ORM trzeba na prawdę orientować się jak działa SQL i w ogóle baza danych. Trzeba wiedzieć, kiedy użyć gotowych rozwiązań ORM, a kiedy warto rozszerzyć daną klasę np. o pobieranie kolekcji nietypowych elementów.
Przykład z życia - masz sklep internetowy w którym produkty przynależą do różnych kategorii, mają różne stawki vat, marki, cechy. Liczba cech produktu jest zmienna. Każdy produkt ma różne progi rabatowe itd itp. Stajesz przed zadaniem pobrania x produktów z bazy z pełną informacją (czyli dodatkowe cechy, progi rabatowe itd). Gdybyś tutaj użył ORM'a bez własnej inwencji to położyłbyś bazę na łopatki ilością zapytań. Pół dnia zajęło mi, aby to zoptymalizować, napisać własne metody pobierania i wypełniania obiektów itd. Ale skutek jest taki, że dowolną ilość obiektów z bazy pobieram 2 zapytaniami Wcześniej jednak musiałem użyć tabel przejściowych, trochę cachowania itd. Mechanizmy Propela pozwoliły mi przebrnąć przez całe zadanie w miarę wygodnie - ale na pewno nie rozwiązały same mojego problemu - po prostu były punktem wyjścia. Swoją drogą było to fajne doświadczenie, bo rozwiązując kolejne problemy przejrzałem kod propela wzdłuż i wszerz - pewne rzeczy wydawały mi się na początku dziwne, ale potem często zauważałem ich uniwersalność i możliwość dostosowania całego narzędzia do swoich potrzeb.
Nie ukrywam, temat dość ciekawy.
Nie wiem jednak czy dobrze to zrozumiałem, a nie chce tkwić w błędzie dlatego napisałem:
<?php class Query { public http://www.php.net/static function http://www.php.net/exec ($sql) { if(http://www.php.net/mysql_connect('localhost','root', 'idesql')) { if(http://www.php.net/mysql_select_db('test')) { return new ORM(http://www.php.net/mysql_query($sql)); } } } } class ORM { private $__dquery = http://www.php.net/array(); public function __construct($query) { $this->__dquery = $query; } public function select() { if($array = http://www.php.net/mysql_fetch_assoc($this->__dquery)) { return new Data($array); } return false; } } class Data { private $__darray = http://www.php.net/array(); public function __get($name) { if(http://www.php.net/empty($this->__darray)) { return false; } if(http://www.php.net/array_key_exists($name, $this->__darray)) { return $this->__darray[$name]; } } public function __construct($array) { $this->__darray = $array; } } $table = Query::http://www.php.net/exec('SELECT * FROM `tabela`'); while($row = $table->select()) { http://www.php.net/echo $row->nr . '; '; http://www.php.net/echo $row->imie . '; '; http://www.php.net/echo $row->nazwisko . ' <br>'; } ?>
<?php $rs = http://www.php.net/mysql_query($sql); while ($row = http://www.php.net/mysql_fetch_object($rs)) { http://www.php.net/echo $rs->id .' '. $rs->name .'<br />'; } ?>
<?php // Użycie sesji, na podobę Hibernate // zauważ, że relacje są przeźroczyste $factory = SessionFactory::build(new PropertiesConfiguration('php-hibernate.ini')); $session = $factory->getSession(); $book = $session->load('Book', 11); $authors = $book->getAuthors(); // tutaj oczekujemy złączenia po relacji M:N $book->setTitle('[nakład wyczerpany]' . $book->getTitle()); $book->setAvailable(false); $authros[0] = new Author('Stanisław', 'Lem'); $session->flush(); // wymuszamy zapisanie zmian // uproszczony przykład użycia Table Data Gateway + Row Data Gateway // relacje M:N są obsługiwane ręcznie $book = BooksPeer::retrieveByPk(11); $authors = $book->getAuthors(); $book->setTitle('[nakład wyczerpany]' . $book->getTitle()); $book->setAvailable(false); // dobieramy się do tabelki pośredniej pomiędzy autorami a książkami $authros[0]->getProxyForBook(11)->delete(); $authros[0] = new Author('Stanisław', 'Lem'); $authros[0]->createProxyForBook(11); // przywiązujemy autora do książki $book[0]->save(); // ten zapis załatwia nam wszystko ?>
Witam,
Chciałbym się odnieść do wypowiedzi paru osób które uważają ze ORM-y to tylko ułatwienie dla programisty, i ze jest ucieczka od nauki SQL-a.
Najważniejszą idea systemów ORM jest wspomaganie idei Domain-Driven-Develpment (DDD) która przeważnie jest podzielona na warstwy takich jak:
Chyba mnie przekonaliście do ORM-a
Propel jest zajebiaszczo prosty w obsłudze, a możliwości są wielkie.
Miałem po prostu złe doświadczenia z ORM'em po poznaniu CodeIgniter'a. A kiedy chciałem poznać Propel to miałem same problemy, a to nie ma rozrzerzenia PHP - xslt (które było) a to co innego ...
W piątek spróbowałem jeszcze raz, a wcześniej jakiś tydzień temu w Symfony. Wszystko działa super
//
Za moje poprzednie posty przepraszam.
Jakie znacie lub macie u siebie zaimplementowane sposoby rozwiązania problemu z relacjami pomiędzy tabelami? Jak rozwiązujecie konieczność pobrania danych z bazy danych przy złożonym warunków opierającym się o dwie tablice?
Takie cos jest w ORMach zaimplementowane. Patrz np propel http://propel.phpdb.org/trac/wiki/Users/Documentation/1.3/Relationships
lub w phpDoctrine
A jak Propel radzi sobie z dziedziczeniem w klasach ?
Przykladowo mam klase
Contractor i dziedziczy po niej Client oraz Supplier
Dostawca i klient maja wiele wspolnych pol wiec warto by bylo
zastosowac tu relacje 1-1.
W efekcie mamy 3 klasy w tym 2 dziedziczace i odpowiadajace im tabele.
O ile nie ma problemu z np.: wstawieniem listy transakcji
do klienta (ralacje 1-n) to nie wiem czy propel
poradzi sobie w sytuacji kiedy to 1 klasa bedzie de facto zapisywac do 2 tabel ?
Jakies doswiadczenia w tym temacie ?
Moze inne ORM niz propel ?
2 temat to kwestja konwersji UML do schematu bazy danych.
Googluje juz od kilku dni ale jedyne co znalazlem to MetaL.
Nie dziala on jednak rewelacyjnie i konwertuje to swojego formatu a nie do schema.xml znanego z propela.
<table name="client" abstract="true"> <column name="client_type" type="INTEGER" inheritance="single"> <inheritance key="0" class="PrivateClient" extends="nazwabazydanych.Client"/> <inheritance key="1" class="CompanyWorker" extends="nazwabazydanych.PrivateClient"/> <inheritance key="2" class="EnterpriseClient" extends="nazwabazydanych.Client"/> </column> <column name="title" type="VARCHAR" size="100"/> </table>
Ponownie wróciłem do tematu.
Widzę, że tutaj nie za dużo się wydarzyło.
Ostatnio zrobilem release tematu podczas tworzenia
abstrakcyjnego kontrolera CRUD do ZF.
Dziwi mnie fakt ze podczas dyskusji o ORM
nikt nie wspomniał o wygodzie przy tworzeniu warstwy widoku.
Przykładowo bibloteka patForms potrafi na podstawie obj. propela
wygenerować nam formularz do wpisywania/edycji danych.
Możemy oczywiście go dowolnie edytować w pliku XML.
Obecnie stosuje zestaw Zend Framework + Propel + Smarty.
Zastanawiam się jednak nad PEAR::DBObject
Testowaliście obydwa pod względem wydajności, wygody użycia ?
Wspomnę jeszcze o DataGrid.
Co polecacie ? Propel dostarcza niby klasę pozwalającą na
integrację z DataGrid z PEAR ale od razu mówie.. wersja jest przestarzała
i lepiej wogóle jej nie ruszać. Ja straciłem 3 h, poznałem dokładnie budowę
DataGrid iw końcu napisałem prawie od nowa klasę integrującą.
Ale teraz efekt jest bardzo przyjemny.
Myślę, że o widoku nikt nie wspomniał, bo temat nie dotyczy widoku :-)
A tak na serio to jeśli już mówimy o wygodzie projektowania aplikacji działających w oparciu o ORM to dla mnie nic nie przebije generacji paneli administracyjnych w symfony
http://www.symfony-project.org/screencast/admin-generator
Kilka linii w pliku konfiguracyjnym i masz wszystko - przeglądanie, dodawanie danych, walidację danych, łączenie danych z wielu tabel po kluczach obcych itd itp.
Jak to pierwszy raz zobaczyłem to się popłakałem, że tyle czasu traciłem na takie bzdety ;-) Zwykły crud to przy tym zabawka.
Inny przykład też z symfony to umieszczenie danych testowych w tabeli z pliku yml - piękna sprawa przy robieniu pierwszych testów.
Kwestia ORM i widoku nie wiąże się pośrednio, ponieważ kluczowe są tu metadane i informacje o strukturach z których ORM korzysta.
Propel ułatwia nam zadanie ponieważ mamy wygenerowany kod z informacjami na temat powiązań itp (bodajże katalog metadata obok innych wygenerowanych klas).
W przypadku ActiveRecord informacje te są zapisane jako fragment definicji w modelu (has_many, many_to_many itp).
Wystarczy zatem bezpośrednio podpiąć się pod metadane i umiejętnie je wykorzystać a nie będziemy potrzebowali generatorów.
Kiedyś dawno, dawno temu dopisywałem adaptery wiążące Propel-Agavi-Smarty tak by tworzyć nowe widoki i wyszukiwarki możliwie łatwo. Dodatkowo powiązałem sobie etykiety z klas z i18n, przez co przy polach wyszukiwania nie musiałem męczyć się z dorzucaniem labeli. Robiły to za mnie po prostu pluginy dopisane do smarty.
Kod, który nie jest wierną kopią tego co napisałem aczkolwiek prezentuje zbliżoną funkcjonalność.
{search for='NazwaKlasy' action=jakiś_url} {field for=id} <- pole z możliwością wpisania tylko intów {field for=foreign} <- tu mi się pokazywał np select {field for=someDate fromTo=true} <- a tu para data od, data do {/search}
{input for=text}
<?php $wrapper = new PropelWrapper($this->getAgaviContext()); try { if ($mode == 'new') { $object = $wrapper->createAndBindObject('Invoice'); } else { $object = $wrapper->readAndBindObject('invoice', 'nazwa_pola_z_id_obiektu'); } // tu otwarcie transakcji $object->save(); // i zamknięcie return 'Success'; } catch (WrapperException $e) { handleWrapException($e); } catch (PropelException $e) { handlePropelException($e); } catch (Exception $e) { handleException($e); } ?>
<?php // tym kodem załatwiamy edycję class InvoiceEditAction extends InvoiceInnerAction { } // tym kodem dodawanie class InvoiceAddAction extends InvoiceInnerAction { } // klasa bazowa dla operacji z fakturą class InvoiceInnerAction extends WrapperAction { // jedyna rzecz jakiej wymaga od nas wrapper protected function getClassName() { return 'Invoice' } } // tym kodem listowanie class InvoiceListAction extends ListAction { protected function getClassName() { return 'Invoice' } } ?>
Używam Zend Frameworka. Chciałbym oddzielić warstwę logiki biznesowej od warstwy dostępu do danych. Dla warstwy logiki biznesowej najlepiej pasowałoby zastosować wzorzec "Domain Model". Jak wykorzystać "Zend_Db_Table" wraz z "Zend_Db_Table_Row" jako warstwy dostępu do danych? Mogę prosić o praktyczne zastosowanie?
Testowałem różne dostępne rozwiązania ORM, najlepiej moim zdaniem wypada DoctrinePHP. Jest bardzo prosty w implementacji, wygodny, ma ogromne możliwości. Także jeżeli komuś zależy na takich "ułatwieniach" to zdecydowanie polecam. Ja jednak zostanę przy pisaniu zapytań i "czystym" PDO. Powód? Pod koniec moich testów odpaliłem sobie Apache Benchmark.
Na bazie testowej (tabele: artykuly, users, tagi, artykuly_x_tagi - jakie tu panują relacje chyba widac ) porównałem wydajność dwóch aplikacji:
1) w moim frameworku (prosty mvc, sesje, pare bibliotek, widok obsluguje TemplateLite) prosta aplikacja pobierająca rekordy z bazy, zapytania w SQL poprzez PDO;
2) te same zapytania w Doctrine i wyniki wyprintowane, bez żadnej "obudowy" w postaci frameworka, także teoretycznie powinno być wydajniej
Wyniki - prawie czterokrotnie większa wydajność pierwszej opcji. Wniosek - ORM to piękna rzecz, ale w dużych projektach gdzie wydajność to priorytet, lepiej sobie darować.
@regis87
I moim zdanie się mylisz, jak jest duży projekt bez ORM to masakra.
A co do wydajności, to jak masz wiele do wielu to tak zawsze jest, ale pamiętaj że korzystając z ORM najbardziej zachłanne rzeczy można przepisać na niższy poziom, czyli wykorzystując tylko abstrakcję bazy danych.
Do tego tak często nie doceniane widoki (przez większość zwane perspektywami) się przy stosowaniu ORM bardzo przydają.
Bo nie można zaprzeczyć, że ORM jest bardziej obciążający niż prosty dostęp, ale tak samo programowanie obiektowe (w tym wspomniane MVC) też jest wolniejsze od proceduralnego, jednak jakoś nikt na to nie zwraca zbytniej uwagi, chyba że jest jakiś punkt gdzie wydajność siada, ale to się małe fragmenty kodu zmienia, nie całą aplikację.
"Nie używajmy ORM", to prawie jakby mówić "piszmy w asemblerze". Może trochę przesadzam, ale jak się nie przestawia korzyści, i gada głupoty że w dużych projektach to lepiej bez... to mnie to irytuje.
Oczywiście zawsze się znajdzie coś co musi być napisane w asemblerze, ale to są na prawdę specyficzne rzeczy, a nie typowe aplikacje.
W 100% popieram. Za całą obiektową otoczką takiego Propela lecą zwykłe zapytania SQL, a jedynie konieczność utworzenie obiektów ze zwracanego wyniku lekko spowalnia. Lepiej zatem (jak mówi Sedziwoj) pisać z użyciem ORM, a potem jedynie odnaleźć bardzo czasochłonne operacje i je przepisać. Zauważ, że jak coś jest bardzo zasobożerne, to jest to zazwyczaj operacja na ogromnych ilościach danych na raz. Z autopsji powiem, bo dziś w nocy troszkę potestowałem, że operacje z wyszukiwaniem LIKE %xxx% na 250 000 rekordach to 3.49 s gołe zapytanie i 3.6 s Propel (wliczam utworzenie paczki z danymi).
Pozdrawiam.
Może w kwestii dużych projektów. Panowie nie zapominajmy, że duże projekty zazwyczaj stoją na dedykowanych maszynach, gdzie nie martwimy się tym, że nasz dostawca odetnie vhosta z racji na generowane obciążenie. Z drugiej strony - jakie są różnice czasowe, bo procentowo jest to dużo, ale czy będzie to odczuwalne dla użytkownika?
Nie pracowałem może w największych projektach, ale ilość linii kodu szła w tysiące czy też dziesiątki tysięcy. Gdyby każdy miał klepać zapytania, bo "tak jest wydajniej" to stracili byśmy na tym zbyt wiele czasu. Podstawą w takich wypadkach jest ORM i dobrze skonstruowane DAO, tak by nie pojawiały się metody duplikujące swe działania (pytanie - które DAO co może odczytywać). Zaoszczędzasz czas upraszczając sobie pracę z pozyskanymi danymi - w dalszym ciągu masz obiekty, w których możesz zawrzeć jakieś w miarę proste operacje, a nie półśrodki w postaci tablic bądź map. Chodzi o to, że gdy ORM stworzy Ci obiekt powiedzmy faktury - tworzysz metodę isPaid a w niej masz jakiś warunek. Normalnie, pisząc strukturalnie - ten warunek przerzuciłbyś do kontrolera bądź, co gorsza, widoku. Gdy coś się zmienia w sposobie uznawania płatności i masz ORM modyfikujesz tylko jedną metodę. Nie szukasz wszystkich warunków, gdzie dany typ danych się przewija.
Popatrzcie proszę na ORM przez perspektywę tego, co może dać nam obiekt .
Może źle to ująłem. Mówiąc o dużych projektach nie mam na myśli bardzo rozbudowanych aplikacji z ogromną funkcjonalnością. Chodzi mi raczej o serwisy, które wystawione są na działanie bardzo dużej liczby użytkowników, a więc wywołań. Sam rozmiar bibliotek ORM daje do myślenia - po skompilowaniu DoctrinePHP do pojedyńczego pliku, zajmuje on prawie 700KB. Bez kompilacji jest jeszcze gorzej, bo wszystko rozbite jest na nie dziesiątki, ale setki plików klas. Z tego co wiem podobnie wygląda to w przypadku Propela.
Ale... jakiś czas temu trafiłem na bibliotekę realizującą wzorzec ORM, o nazwie Pork. Jest bardzo mała, szybka... trzeba to przetestować
http://www.schizofreend.nl/Pork.dbObject
W przypadku Propela masz kilka plików + te, które sam wygenerujesz.
Rozumiemy, o co chodzi z dużą ilością odwiedzin, ale w którym momencie upatrujesz koniec sensowności ORM? Ile odwiedzin dziennie?
Nie potrafię tego oszacować. Ale kiedy wydajność staje się problemem, w ORM w pierwszej kolejności szukałbym oszczędności...
Jeśli dysponujesz dedykowanym serwerem, to moim zdaniem problemy zaczną się gdzieś w okolicach kilkudziesięciu tysięcy odsłon dziennie. Bardzo mało serwisów osiąga takie liczby. Ja bym w tym wypadku zainwestował w jakiś cache dla modelu danych, czy nawet w cachowanie całych stron www. Dopiero gdyby to nie pomogło, szukałbym optymalizacji zapytań. Nie będę rzucał liczbami, ale np. forum.php.pl mimo, że jest popularnym, średniej klasy forum nie osiąga jeszcze pułapu, przy którym zdycha sprzęt ;p
Pozdrawiam
<?php class Produkt extends ActiveRecord {} class Zamowienie extends ActiveRecord { public function obliczWartoscZamowienia() { $produkty = $this->pobierzZamowioneProdukty(); $wartosc = 0 foreach ($produkty as $produkt) { $wartosc += $produkt->pobierzCene() * $produkt->pobierzIlosc(); } return $wartosc; } } ?>
@up: TAK
Skończyłem testy, http://www.schizofreend.nl/Pork.dbObject. Jest imponujący pod względem wydajności i małych rozmiarów, ale ma ten mankament, że aby obsługiwać relacje pola w tabelach muszą być nazywane według ustalonego schematu. Ten schemat tak naprawdę wymusza PRAWIDŁOWE nazewnictwo pól: pole primary nazywa się ID_obiekt, pola będące kluczami relacji mają nazwy odpowiadających kluczy primary itd... ale wiadomo jak to wygląda w praktyce, rzadko zdarzają się tak ładnie skonstruowane bazy
Autor mówi, że to właśnie takie ograniczenie pozwala na tę oszczędność kodu. Ale w planach ma dodanie funkcjonalności tworzenia relacji "ręcznie".
To może ja coś skrobnę od siebie, o moich doświadczeniach z ORM, a konkretnie z http://www.phpdoctrine.org/. Obok wcześniej wspominanego Propela jest to jedeno z najpopularniejszych narzędzi tego typu. Doctrine przeciwieństwie do Propela nie generuje żadnego kodu. Oraz właściwie nie uwalnia nas do końca od pisania SQL. Jednak MASYMALNIE go uprasza i przyśpiesza. Mianowicie, w Doctrine definuje się każdą tablę np. tak (przykłady będą z dokumentacji)
<?php class Article { public function setTableDefinition() { $this->hasColumn('name', 'string'); $this->hasColumn('content', 'string'); $this->index('content', http://www.php.net/array('fields' => 'content', 'type' => 'fulltext')); } } ?> <?php class Product extends Doctrine_Record { public function setTableDefinition() { $this->hasColumn('id', 'integer', 4, 'primary'); $this->hasColumn('price', 'decimal', 18, http://www.php.net/array('min' => 0, 'max' => 1000000)); } } ?>
<?php $q = new Doctrine_Query(); $q->from('User u') ->leftJoin('u.Group g') ->innerJoin('u.Phonenumber p WITH u.id > 3') ->leftJoin('u.Email e'); $users = $q->execute(); //używanie danych, przykładowo ilustruje jak to wygląda. Nie wiem jakie są pola w
bazie dancyh. $users[0] -> name; $users[0] -> Phonenumber -> number; ?>
person.gender = 'M' AND (person.location IN ('Birmingham', 'Coventry') OR person.location = 'Manchester') AND (person.enabled <> 0) AND person.age > 16
<?php $c = new Criteria(); $crit0 = $c->getNewCriterion(PersonPeer::GENDER, 'M'); $crit1 = $c->getNewCriterion(PersonPeer::LOCATION, http://www.php.net/array('Birmingham', 'Coventry'), Criteria::IN); $crit2 = $c->getNewCriterion(PersonPeer::LOCATION, 'Manchester'); // Perform OR at level 1 ($crit1 $crit2 ) $crit1->addOr($crit2); $crit3 = $c->getNewCriterion(PersonPeer::ENABLED, 0, Criteria::NOT_EQUAL); $crit4 = $c->getNewCriterion(PersonPeer::AGE, 16, Criteria::GREATER_THAN); // Perform AND at level 0 ($crit0 $crit1 $crit3 $crit4 ) $crit0->addAnd($crit1); $crit0->addAnd($crit3); $crit0->addAnd($crit4); // Remember to change the peer class here for the correct one in your model $c->add($crit0); $result = TablePeer::doSelect($c); ?>
Doctrine nie używałem więc trudno mi porównać prostotę pisania. Ale skłonię się ku opinii Sedziwoja. Dla mnie w Propelu największym plusem są właśnie klasy "szkieletowe" z możliwością ich rozbudowania. Najwięcej czasu w projekcie zawsze traciłem na tworzeniu prostych zapytań. Jest to (obok formularzy) jedna z najbardziej monotonnych części projektu.
Propel generuje w zasadzie wszystko co się da zrobić z automatu, a programiście pozostaje tylko dopisanie ewentualnych nakładek i dodatków.
Genialną zaletą Propela jest właśnie podpowiadanie składni (propel pozwala opcjonalnie nawet na dodanie komentarz phpDoc więc w PDT podpowiadanie działa nawet do zagnieżdżonych obiektów). Dzięki podpowiadaniu składni, nawet po powrocie do projektu po kilku miesiącach łatwo jest ogarnąć operacje na bazie.
Wady propela jak dla mnie są dwie:
- dosyć skomplikowane tworzenie niektórych zapytań (np. gdy przychodzi do łączenia Criterion'ów to do tej pory muszę czasami zaglądać do manuala)
- wydajność (chodzi o samo php, bo zapytania są raczej standardowe) - choć tak na prawdę wydajność to taki problem "urojony", bo w przeciętnej wielkości stronie, i tak nie ma to aż tak dużego znacznie. Gdybym pisał aplikację pod bardzo duże obciążenie to może bym się tym przejmował.
Nie używałem Doctrine, ale Propela owszem i rzeczywiście - bardziej złożone zapytania wyglądają jeszcze bardziej zawile w propelu niż w gołym SQL. Trzeba jednak zauważyć dwie rzeczy
1. Bardzo złożone zapytania można również napisać ręcznie, a potem na podstawie wyniku wypełniać obiekty. Propel ma do tego wsparcie.
2. Złożone zapytania stanowią w większości systemów margines. Są to wszelkiego rodzaju wyszukiwania wg. złożonych kryteriów, którym nie podoła żadna forma obiektowa (w sensie zachowania prostoty).
Mitem jest, że Propel ma słabą wydajność. Relacje są ustawiane sztywno w wygenerowanych klasach, a wysyłane zapytania niczym sie nie różnią od normalnych zapytań. Późniejsze wypełnienie obiektów trwa tyle samo, ile by zajęło zrobienie tego ręcznie. Można jednie polemizować, czy operacje na 1000 rekordów należy wykonywać z pominięciem kreacji obiektów, czy nie.
Wadą, którą widzę w Doctrine jest to, że trzeba pisać kod php dla każdej tabeli. Nie ma żadnego generatora? Naprawdę bardzo fajnie koduje się strukturę z XML, czy nawet w YAML (o to nawet polubiłem). Już nie wspomnę i łatwiejszej edycji takiej struktury:)
Pozdrawiam.
No niestety sama wydajnośc propel to nie do końca mit. Popatrz chociażby na ilość kodu klas generowanych przez propela. Samo załadowanie takiej ilości kodu to już niezłe obciążenie. Niektórych przypadkach propel też nie grzeszy wydajnością np. http://blog.dywicki.pl/2006/09/21/propel-12-przyspiesz-go-nawet-do-2-razy/.
Ale tak jak pisałem wcześniej - to nie jest problem przy standardowej, dobrze napisanej stronie. Problemem może się natomiast okazać gdy na jednym hostingu próbujesz upchnąć 10-15 stron klientów itp., ale dla mnie to nie jest problem.
Więc Doctrine ma możliwość definiowania schematu w pliku YAML (w przeciwieństwie do propela). Dodatkowo ma jeszcze wbudowany mechanizm migracji.
Tak jak pisał athabus, najwięcej czasu zajmuje napisanie tych prostych zapytań. Kiedyś sobie tesowałem, i wyszło mi, że szybciej (i krócej) jestem w stanie napisać takie zapytanie w Propelu niż w Doctrine.
Dodatkowo Propel (oczywiście w edytorze z podpowiadaniem składni) nie pozwala nam popełnić literówki, Doctrine jednak tak. Ale racją jest, że trudne zapytania w Propelu to duży problem.
Jeśli chodzi o wydajność to Propel 1.2 jest generalnie wolniejszy od Doctrine, ale już Propel 1.3 jest szybszy (oczywiście nikt nie musi zgadzać się z http://notjosh.com/blog/archives/34-Propel-1.3-beta-in-Symfony!.html benchmarkiem.
Jeśli idzie o szybkość Propela w wersjach <= 1.2 to rzeczywiście nie było z nią najlepiej. Wszystko było spowodowane nienajlepszą konstrukcją "bindowania" z wyniku zapytania do obiektów. Zbędne iteracje i porównania robiły swoje, w wersji 1.3 całość wygląda znacznie lepiej.
Zapytania - Panowie zapominacie o najważniejszej zalecie Criterii. Można w bardzo prosty sposób stworzyć mechanizm tworzący na bazie wysłanego formularza kryteria do wyszukiwania i sortowania. Kiedyś taki mechanizm stworzyłem, wystarczyło stworzyć formularz a po stronie serwera powiedzieć które tabele będą używane. Mechanizm na podstawie nazw pól tworzył wyniki (porównania, mniejszy-większy, like, plus ograniczenia na relacje). Następnie warunki wędrowała do sesji (tablice o określonej strukturze), gdy ktoś wracał na stronę z wynikami trafiał dokładnie w to samo miejsce gdzie był np. przed edycją, bo stworzenie Criteria z tablicy było tylko formalnością. Z takim mechanizmem tworzenie widoków z listami zajmowało mi około 15 minut (formularze do wyszukiwania plus wyświetlanie wyników). Rezultatem tego było to, że później widoki (listy) mogła robić osoba, która miała pojęcie o html i smarty.
Wykorzystajcie Criterię nie tylko do tworzenia zapytań, na prawdę warto zainwestować w jakiś automatyczny mechanizm jeśli jeszcze go nie macie. Parę komponentów do warstwy prezentacji, parę klas, kilka pętli a zysk czasowy na prawdę znaczny.
Do tego skomplikowane zapytania można oprzeć na widokach, a zdefiniować takie tabele w XML/YAML jako tylko do odczytu. Wtedy nie ma tego kodu po stronie PHP. @splatch dokładnie, wystarczy mały generatorek Criteria i mamy właściwie od ręki listy z filtrowaniem i stronicowaniem. Wystarczy powiedzieć jakie pole odpowiada któremu w bazie i jakie warunek porównania.
Ale tak jeszcze podkreślę, pamiętajcie że baza ma też coś więcej niż tabelki, więc wykorzystujcie te mechanizmy aby ułatwić sobie życie.
@Strzałek tylko powiedz w czym to jest lepsze od Propela? Bo jak już mówiłem to porównanie jest ważne, aby wykazać co jest lepsze. Bo ciągle jako porównanie bierzesz system bez ORM (źle się pisze bez klawiatury)
Uh, jak mówiłem - nie używałem Doctrine. Jak jest generator, to ok . W takim wypadku kwestia wyboru pomiędzy Propelem, a Doctrine, to jak wybór pomiędzy różnymi systemami szablonów ;] Jak będę miał czasa to zerknę na Doctrine i zobaczę co tak wychwalacie ;p
Pozdrawiam.
Oj tam
Mi podpowiadanie składni w eclipse działa jak mu się podoba i to w momentach, gdy nie jest mi potrzebne, więc nie przekonuje mnie ten argument. Bardziej patrzę na funkcjonalność samej biblioteki.
Pozdrawiam.
p.s
Ja pisze z nowej klawiatury - dwa dni temu kupiłem
Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)