Egy hét Drupal 7: Az entitásról

Most, hogy gőzerővel készülök az Integral Vision Workshop rendezvénysorozatunkra, úgy gondoltam megosztok egy pár érdekességet, gondolatot a Drupal 7 verziójáról.

Első körben beszéljünk az entitásokról.

A Drupalban eddig mindenki tudta, hogy ha tartalom kezelésről volt szó, akkor azt a legkönnyebben a nodeok segítségével tudta megoldani. Az elgondolás lényege az volt, hogy adott a node, melyet a különböző modulok adatelemekkel és funkciókkal tudnak bővíteni. Ez nagyszerű volt, hisz ha volt egy oldal típusú tartalmam, vagy egy hír típusú, akkor ha az egyikhez megírtam a hozzászólás funkciót akkor az a másikkal is működött. Csak arra kellett figyelnem, hogy ezt úgy tegyem meg, hogy azt a típus nélküli nodehoz írjam meg. Ettől fogva a hírhez is és az oldalhoz is hozzá tudtam szólni, hisz mindkettő node volt. Sőt, amennyiben létrehoztam egy új tartalom típust, mondjuk képet, vagy képgalériát, akkor már azokhoz is igénybe vehettem ezt a nagyszerű szolgáltatást.

Azonban, maradt számos olyan tartalmi elemem, melyek nem voltak nodeok. Ilyenek voltak a kategóriák, a hozzászólások, felhasználók stb. Amennyiben ezekhez is igénybe kívántam venni azokat a szolgáltatásokat amiket a nodeokhoz kidolgoztak, trükköznöm kellett.
(Ilyen szolgáltatás volt például az, hogy az adott tartalmi elemhez újabb mezőket adjak hozzá, vagy megjelenítsem egy listában. Vagyis a CCK és Views modul)

A legalapabb trükk az volt, hogy az adott adatelemből nodeot gyártottunk. Ezzel aztán meg is oldottuk a problémánkat. Amennyiben társkereső oldalt akartunk készíteni, semmi más dolgunk nem volt, mint feltenni a Usernode(4.7.x, 5.x), vagy Content Profile(6.x) modulok egyikét. Ekkor minden egyes felhasználóhoz létrejött egy megfelelő típusú node. Semmi mást nem kellett tennünk, mint felvenni egy újabb CCK választó mezőt, melyben bejelölhették a delikvensek, hogy ők a focit szeretik vagy a baseballt és egy ügyes views lista segítségével már egymásra is találtak.

Azonban bármilyen szépen működött is ez a dolog, volt pár árnyoldal.

Először is, mivel két külön adattáblába, két külön modul írogatta azokat az adatokat amelyeket egy táblában kellett volna tárolnunk, igen magasra szökött az inkonzisztencia veszélye. Keletkeztek olyan felhasználók, akikhez nem tartozott node és keletkeztek olyan user nodeok, amikhez nem tartozott felhasználó. Amennyiben nem figyeltünk oda, könnyedén létrehozhattunk egy felhasználóhoz több tartalmat is. Egyszóval, volt egy olyan szuper bárhogyan konfigurálható rendszerünk amihez igazából jobb volt, ha nem nyúltunk hozzá.

A másik probléma ezzel a megoldással, hogy ha kevés hírünk volt, de sok felhasználónk, akkor is keményen dolgozott az adatbázisunk, hisz a hírek megjelenítéséhez először ki kellett szűrnie a felhasználókat az adattáblából és csak utána tudta rendezni és kilistázni a híreinket. Amint egyre több és több felhasználónk lett, úgy lett egyre nehezebb és nehezebb kilistázni a híreinket.

A „Csináljunk mindenből nodeot” megoldásnak ezen felül még volt egy árnyoldala. A node számos olyan frankó funkcióval rendelkezett, amire nem minden esetben lett volna igényünk. Ilyen szolgáltatások voltak azok, hogy egy node az verziókezelt, lehetett tudni melyik tartalmat melyik felhasználó látta már, lehetett szabályozni különböző szempontrendszerek szerint, hogy mely tartalmat mely felhasználó nézhess stb. Egyszóval számos olyan, ami egy tartalom kezelésekor jól jött de nem igazán volt szükségünk akkor erre, ha mondjuk felhasználókról, vagy csoportokról volt szó.

Drupal hetesben bevezetésre került az entitás fogalma, melyet egy node feletti egységként kéne elképzelni. Így lehetővé vált az, hogy a megvalósítani kívánt funkcióknál eldönthessük, hogy az melyik tartalmi szinten lesz számunkra érdekes. Ezek egy részét ráadásul az entitás létrehozásakor mi magunk is szabályozhatjuk. Pl. azt, hogy lehessen-e hozzá mezőket kapcsolni vagy legyen-e verziókezelt.

Minden entitásnak vannak olyan altípusai – úgynevezett bundle –, melyekhez a FieldAPI segítségével különböző mezőcsokrokat kapcsolhatunk. A node-nál ez a node típus, a kategóriáknál a szótár a felhasználóknál meg a ... hopp és itt a rés a pajzson, mert nincsenek a felhasználónak altípusai. Természetesen, nem lenne Drupal a Drupal, ha nem lenne egy alter hook amivel ne lehetne ezen a szörnyű helyzeten is változtatni.

De ne szaladjunk ennyire előre, ezt majd a workshopon. Készítsünk egy olyan Drupal 7 modult, ami egy olyan pehelysúlyú tartalmi elemet – egy entitást – valósít meg, mely semmi más mint egy darab táblában egy darab mező. Nézzük az én megoldásomat!

Először is kelleni fog egy info fájl:

  1. name = Entity Próba
  2. core = 7.x
  3. files[] = entity_proba.module
  4. files[] = entity_proba.test

Figyeljük meg, hogy a hetesben meg lehet adni az info fájlban, hogy a modulunknak melyik fájlokra lesz majd szüksége. Az .install fájl nem szerepel a listában, hisz a benne található kódokra csak akkor van szükség, amikor először bekapcsoljuk a modult, vagy eltávolítjuk. Itt fogjuk megadni az adatbázis séma definicíóját:

  1. function entity_proba_schema() {
  2. $schema['entity_proba'] = array(
  3. 'description' => 'The base table for entitys.',
  4. 'fields' => array(
  5. 'epid' => array(
  6. 'description' => 'The primary identifier for a Entity proba.',
  7. 'type' => 'serial',
  8. 'unsigned' => TRUE,
  9. 'not null' => TRUE,
  10. ),
  11. 'title' => array(
  12. 'description' => 'The title of this entity.',
  13. 'type' => 'varchar',
  14. 'length' => 255,
  15. 'not null' => TRUE,
  16. 'default' => '',
  17. ),
  18. ),
  19. 'primary key' => array('epid'),
  20. );
  21. return $schema;
  22. }

Mint látható, egyetlen egy táblát hozunk létre, melyben két mező van. Az epid, amely az egyedi kulcsa lesz az egyes soroknak és a title amiben a tulajdonképpeni adatot fogjuk tárolni.
No akkor nézzük a modult:

  1. function entity_proba_entity_info() {
  2. $return = array(
  3. 'entity_proba' => array(
  4. 'label' => t('Entity Próba'),
  5. 'base table' => 'entity_proba',
  6. 'entity keys' => array(
  7. 'id' => 'epid',
  8. 'label' => 'title',
  9. ),
  10. ),
  11. );
  12. return $return;
  13. }

Egyetlen egy hookot valósít csak meg a modulunk, ez pedig a hook_entity_info(). Ennek a segítségével tudjuk megadni, hogy milyen entitást vagy entitásokat valósít meg a modulunk. Mivel ez egy kis tanuló modul, ezért csak egyetlen egy entitást valósítunk meg, annak is csak a legminimálisabb adatait adjuk itt meg. Pont annyi adatot, amennyivel a táblában található adatokat az entity_load() függvény segítségével be lehet majd tölteni.

Felmerülhet bennünk a kérdés, hogy hogyan tudjuk ezt kipróbálni, hogyan bizonyosodunk meg arról, hogy ez helyesen működik, vagy működik-e egyáltalán. Erről lesz szó holnap.
Addig is a csatolt modult lehet nézegetni, módosítani, próbálgatni.

CsatolmányMéret
entity_proba.zip1.57 KB

Hasznos sorozatnak

Hasznos sorozatnak ígérkezik.

Sajnos D7-ben az elnevezések és a fordítás nem segíti a változások megértését. Én magamban az entity-t dolognak hívom, a bundle-t pedig (dolog)típusnak. Ez sem tökéletes, de nálam működik.

- Felhasználó (dolog)
-- Ember (típus)
-- Tündér (típus)
-- Manó (típus)

- Szöveges tartalom (dolog)
-- Cikk (típus)
-- Oldal (típus)

A típusok esetében pedig mindig arra gondolok, hogy ezek NEM taxonómia kategóriák vagy felhasználói csoportok. Az "adminisztrátorok" csoportnak lehetnek ember és tündér tagjai is. A "magunkról" kategóriába kerülhetnek cikkek és oldalak is. Stb. Az elején a legnehezebb a fogalmak átrendezése az ember fejében.

Hasznos cikk. De egyből

Hasznos cikk.

De egyből kérdések merültek fel bennem:

A másik probléma ezzel a megoldással, hogy ha kevés hírünk volt, de sok felhasználónk, akkor is keményen dolgozott az adatbázisunk, hisz a hírek megjelenítéséhez először ki kellett szűrnie a felhasználókat az adattáblából és csak utána tudta rendezni és kilistázni a híreinket. Amint egyre több és több felhasználónk lett, úgy lett egyre nehezebb és nehezebb kilistázni a híreinket.

No de ("node" :) ) egy rendesen indexelt táblában, jól működő adatbázis esetén nem kellene, hogy gondot jelentsen, ha akár többszázezer rekord közül szűrünk, vagy itt arra gondolsz, hogy a node.type mezőben igazából stringalapú keresés (pl. "story", "product", "image", stb.) történik, és az erőforrás-igényes? Mert végül is az jogos lehet, biztos lassabb, mint egy integer alapú keresés, bár nem tudom, pontos stringegyezőségnél mi a helyzet, query cache-sel, stb.

files[] = entity_proba.module

Ezt miért rakod a .info fájlba? Úgyis betöltődik a MODULODNEVE.module fájl minden modulnál.
Ezenkívül azért is érdekes ez a megoldás számomra, mert alapvetően úgy tudom, ez a files[] tömb inkább class autoloader. Több modulnál is láttam már azt a megoldást, hogy amit alapvetően SZERINTEM module_load_include()-dal indokoltabb lett volna include-olni, azt inkább a files tömb felhasználásával töltöttek be.
De homályosítsatok fel akkor ennek a lényegéről, ha valamit rosszul értek.
Mindenesetre drupal.org-ról, a "Writing module .info files (Drupal 7.x)" cikkből:
http://drupal.org/node/542202

files (Optional)
Drupal now supports a dynamic-loading code registry. To support it, all modules must now declare any code files containing class or interface declarations in the .info file, like so:
name = Really Neat Widget
...
files[] = example.test

When a module is enabled, Drupal will rescan all declared files and index all the classes and interfaces that it finds. Classes will be loaded automatically by PHP when they are first accessed.

Előre is köszi, ha reagálsz (reagáltok)!

"No de ("node" :) ) egy

"No de ("node" :) ) egy rendesen indexelt táblában, jól működő adatbázis esetén nem kellene, hogy gondot jelentsen, ha akár többszázezer rekord közül szűrünk"

Itt maga a szűrés is egy felesleges dolog. Ha van 10 hír és 10000 felhasználó, akkor miért is a node táblába tárolom a felhasználókat, holott az egy külön etnitás. Megoldható? Meg. Gyors? Gyors. De vannak esetek amikor ez a kicsi is sok. Hatosnál ezt nem tehettem meg, mert csak a node-hoz lehetett mezőket adni. A hetesben pedig könnyedén meg tudom ezt tenni, erről szól a cikk. Ma már nem így írnám. :)

files[]

Nos igen, ez egy hiba. Figyelmesebb lehettem volna, hisz időközben javítottak a doksin:
http://drupal.org/node/542202/revisions/1293362/view

Ma már ezt se így csinálom, tanítom. :)

pp

Köszi szépen a választ!

Köszi szépen a választ! Örülök, hogy reagáltál.

Ezt jó tudni, hogy a doksiban eredetileg helytelen infó szerepelt! Ez magyarázatot ad arra is, hogy egyes moduloknál vajon miért alkalmazzák ezt a mai napig. Arra mondjuk nem, hogy bizonyos modulok fejlesztői miért alkalmazzák ezt a files[] tömböt előszeretettel module_load_include-olás helyett, hiszen akkor nem az eredeti koncepciónak megfelelően használják.

A node táblával kapcsolatos szempont valamennyire jogos, de én megmondom őszintén, ha belenézek a node táblába, akkor nem sok "tuningolást" látok a 7-esben a 6-oshoz képest, ha már az erőforrásokkal spórolunk. Például ami előtt én értetlenül állok, az két mező: a type és a language mező:
http://i.imgur.com/8lkPduF.png
A type-ban és a language-ben miért stringekkel tároljuk el a megfelelő értékeket? Van egy node_type táblánk is, amiben szépen külön eltároljuk a content type-ok neveit. Ez tök jó, de miért nincs ezeknek a típusoknak egy integer azonosítójuk? Miért nem ezeket a nem létező integer azonosítókat tároljuk a node.type mezőben?
A nyelveket miért nem tároljuk külön, például egy languages táblában, ahol szerepelhetnének az 'und', 'en', 'de', 'hu', stb. értékek stringként, és ezekhez szintén mind tartozna egy integer azonosító? És miért nem ezeket a languages táblában tárolt azonosítókat rakjuk bele a node.language mezőbe?
Ezekkel mind-mind lehetne finomhangolni, mivel az integer alapú keresés gyorsabb, mint a stringalapú keresés (bár lehet, hogy a MySQL ezt valahogy szépen elintézi magának a háttérben, nem tudom), és a táblákat gyönyörűen lehetne joinolni, és nem lennének ilyen csúnyák a táblák (tudom, ne nézegessem :) ). De jelenleg ha 100 article típusú tartalmam van, akkor 1-szer szerepel az "article" string a node_type táblában, és 100-szor stringként a node táblában; ezenkívül ha nyelvfüggetlen ez a 100 cikk, akkor 100-szor szerepel az "und" karaktersorozat...
Most csupán két példát ragadtam ki, de még sajnos jópár ilyet lehetne említeni.

Lehet, hogy egész érdekes előadástéma és megfelelő flamewar-alap lehetne a "További javasolt finomhangolási lépések a Drupal adatbázisának továbbfejlesztéséhez" :))
Persze nálam jóval okosabb emberek tervezték meg az adatbázist, csak sok esetben értetlenül állok előtte, hogy miért úgy, ahogy.

Bocsánat, hogy eltértem a témától, csak ez kikívánkozott, alapvetően költőiek a kérdések, de hátha tudsz róluk valamit. :)

Nem szeretnék okoskodni

Nem szeretnék okoskodni (pláne nem más blogjában), de pár dologra reagálnék.

Az egyik az adatbázisban az elsődleges kulcs szerepe. Az elsődleges kulcs arra való, hogy az adattábla egy adott értékét egyértelműen beazonosítsa. Nem kell annak numerikusnak lennie, mert úgyis index készül hozzá, ami az elérést minden esetben optimalizálja. Igazából numerikus, egyesével növekvő értéket csak akkor szoktunk használni, ha külső rendszerek felé nincs szerepe (nem kell a Drupalt más rendszerrel integrálni) vagy nincs jobb. Pl. a tartalmaknál, ahol minden szinte változhat, a numerikus érték adja magát. Viszont a nyelveknél már nem, mert minden nyelvnek van egyedi, ISO 3166 szabványban rögzített kódja, ami a kulcs szerepét remekül teljesíti.
Szóval itt az általad emlegetett „stringalapú keresés” itt nem játszik, viszont elkerültük a túloptimalizációt, vagyis hogy egyedi azonosítót hoztunk be olyanhoz, aminek már van egy egyedi azonosítója.

Sőt, van a történetnek egy másik aspektusa is: Exportálhatóságnak hívják. Szerencsére a Drupal kezd leszokni arról, hogy mindenhol numerikus azonosítót használjon, ehelyett vannak a „machine name”-k. Ha egy adott numerikus azonosító két oldalon mást jelent (pl. szerepkörök), akkor nem lehetséges teljes biztonsággal a két oldal között beállításokat hordozni.

Hozzászólás

A mező tartalma nem nyilvános.
  • A webcímek és email címek automatikusan linkekké alakulnak.
  • A sorokat és bekezdéseket a rendszer automatikusan felismeri.
  • You can enable syntax highlighting of source code with the following tags: [code], [blockcode].

További információ a formázási lehetőségekről

Refresh Type the characters you see in this picture.
Type the characters you see in the picture; if you can't read them, submit the form and a new image will be generated. Not case sensitive.  Switch to audio verification.