lworm (egy pehelysúlyú PHP ORM megoldás)

Amikor elkezdett foglalkoztatni a gondolat, hogy PHP-ban fejlesszek Google App Engine-re, az egyik legnagyobb problémának az adatbázis elérése tűnt, hiszen PHP-ból általában SQL használunk erre, ami ugye App Engine alatt nem megoldható, hiszen a datastore egy nem relációs adatbázis. Utánanéztem kicsit, hogy mások hogy oldják meg ezt, és igazából két megoldást találtam. Az egyik az App Engine által szolgáltatott JPA vagy JDO réteg használata. Itt ugye az a gond, hogy ezzel elveszítjük a rendszer hordozhatóságát, hiszen egy standard LAMP környezetben már nem állnak rendelkezésre ezek a programkönyvtárak. A másik lehetséges megoldás, hogy valamilyen SQL wrappert használunk, amin keresztül a datastore szabvány SQL adatbázisnak tűnik. Ezzel az volt a gondom, hogy feleslegesnek tűnt, hiszen az SQL réteg felett úgyis valamilyen ORM réteget használnék, és ha már így van, miért ne építsük az ORM réteget közvetlenül a datastore fölé. Így született tehát az elhatározás, hogy saját ORM réteget fejlesszek PHP-hoz, és ha már belefogtam, megpróbáltam belegyúrni az eddigi tapasztalataimat.

Az első szempont az egyszerűség volt. Minimalista megoldást akartam, ami egyszerű, de mégis hatékony. E mellett legyen transzparens, tehát az elkészült PHP alkalmazás egyaránt képes legyen futni egy általános LAMP környezetben, és az AppEngine datastore-ja felett is. Mivel annak idején a Hibernate-el volt némi nézeteltérésem, mindenképp olyan megoldást akartam, ahol az entitások memória objektumok, tehát nincsenek az adatbázishoz kötve (csúnyán mondva nem attach-oltak). Itt most nem térnék ki a részletekre, de a lényeg, hogy ennek köszönhetően az adatbázis objektumokat büntetlenül tárolhatjuk a session-ben, vagy mondjuk JSON formában mozgathatjuk őket két rendszer között. Kb. ezek voltak az előfeltételek, lássuk hát a megvalósítást.

A rendszer használatához elsőként egy YAML állományban le kell írnunk az adatbázis sémát és az entitások kapcsolatrendszerét. Ez a YAML formátum felépítésének köszönhetően nem túl bonyolult, mégis szép és átlátható. A YAML alapján a ModelGenerator osztály használatával legenerálhatjuk az entitás osztályokat. Az entitás objektumok csak egyszerű adattároló szerepet töltenek be, így felépítésük az adatbázis megvalósítástól független. Ha ezzel megvagyunk, a megfelelő Datastore factory hívásával kérhetünk a rendszertől egy datastore példányt, aminek segítségével az adatokkal kitöltött entitás az adatbázisra menthető, illetve onnan felolvasható. Ugyancsak a datastore segítségével generálhatunk lekérdezéseket, aminek segítségével egyenlőre csak szűrni és rendezni lehet az eredményeket. Ennek az az oka, hogy az alacsony szintű datastore API csak ezeket teszi lehetővé. Bár lehet, hogy ez az eszközkészlet kicsit szegényesnek tűnik, de az esetek nagy részében általában elég, vagy némi kódolással kiegészíthető. Az entitás példányok kapcsolatainak kezelésére azt találtam ki, hogy az entitás objektum minden kapcsolatához egy metódust rendeltem, ami paraméterként kapja meg az aktuális datastore-t. A metódus egy kezelő objektumot ad vissza, ami a kapcsolatnak megfelelően karbantartja a kapcsolódó objektumokat. Tehát vegyük például a User (felhasználó) és Role (szerepkör) entitásokat, melyek közt many-to-many (több-több) kapcsolat van. Ebben az esetben a User osztálynak lesz egy getRolesRelation metódusa, ami egy kezelő objektumot ad vissza. Ezen az objektumon keresztül adhatunk hozzá Role-okat az adott felhasználóhoz, ezen keresztül listázhatjuk ki a hozzárendelt szerepköröket, vagy törölhetjük azokat. Mivel ezek a Relation objektumok mindig létrehozáskor kapják meg a szükséges adatbázis kapcsolatot, az entitásnak magának nem kell az adatbázishoz kötődnie, amiről az előzőekben már írtam. Röviden tehát körülbelül ennyi.

Az eredmény tehát egy nagyon kompakt kis ORM rendszer. Jelenleg a MySQL-t és a Google App Engine Datastore-ját támogatja, de nagyon egyszerűen kiterjeszthető. Maga a kód nagyon egyszerű, áttekinthető és könnyen használható. Bár ennek a lehetőségét nem vizsgáltam meg mélyebben, de a csökkentett eszközkészletnek köszönhetően valószínűleg a datastore-on kívül más nem relációs adatbázisokra is alkalmazható lenne a rendszer, pl. MongoDB-re. A későbbiekben talán érdemes ezt az irányt is megvizsgálni. A programkönyvtár másik nagy előnye, hogy nagyon pici, így akár kódméret kritikus rendszerekben is használható lenne. A későbbiekben lehet, hogy készül Java, Python, esetleg C++ változatot, amit például mobiltelefonok programozásához lehetne használni.

A kód és néhány példa elérhető a Google Code-on a http://code.google.com/p/lworm/ címen.