Varnish: érd el a maximális oldalbetöltési sebességet!

Mielőtt belefognánk…

A webről érkező kérések kezelése (különösen, ha dinamikusak) a webszervereken (pl. Apache) a rendszer-erőforrásokat igencsak igénybe veheti, emiatt egyre többen kezdték használni a könnyűsúlyú webszervereket (pl. ngix, nginx vagy lighttpd). Ezek az Apache rendszerrel összekötve statikus tartalomnál frontendként, vagy önállóan is működhetnek.

Azonban a weboldalad vagy webshopod javítására létezik egy másik módszer is: használhatsz webszerever-gyorsítót, mint a Varnish Cache, melyet a Varnish Software fejlesztett.

A Varnish Cache folyamatainak ismerete többféleképpen is elsajátítható: Varnish Book olvasása, Hivatalos dokumentáció, sok gyakorlás és kísérletezés. Különösen ajánlott az első kettővel kezdeni. Ennek a cikknek az anyaga mindegyik forrásból merít: pl. stackoverflow-n és más szakmai oldalakon található anyagokból, valamint személyes tapasztalatból is.

 

Mi a Varnish?

 

A VARNISH: egy nyílt forráskódú, fordított HTTP proxy cachelési szoftver, olykor webapplikáció-gyorsítóként is említik. A fordított proxy egy olyan proxy szerver, amely a látogatók számára egy szokásos szervernek tűnik. A memóriában fájlokat vagy részállományokat tárol (cache-el), melynek köszönhetően lecsökken a válaszidő és a hálózati sávszélesség-használat.

A Varnish Software szakmai támogatást is nyújt a Varnish-t használatához, amit egyébként egy kétzáradékos BSD-licensz szabályoz. A Varnish 1.0 2006-ban, a Varnish 2.0 2008-ban, a Varnish 3.0 2011-ben, a Varnish 4.0 pedig 2014-ben jelent meg.

A Varnis igen rugalmas, hiszen saját magad tudod hozzá megírni a cache-elési eljárást a Varnish Konfigurációs Nyelven (Varnish Configuration Language /VCL/), ami egy C nyelven alapuló alkalmazásspecifikus nyelv. Ez később aztán C kóddá fordul kompilálás előtt.

 

Hogyan működik a Varnish?

 

Tegyük fel, hogy a látogató egy kérést küld a szerveredre. Ezt először a Varnish kapja meg és a gyorsítótárban keres rá választ: ha talál érvényes tartalmat, akkor elküldi válaszként, ha nem, akkor továbbítja a kérést a backend felé. A backend válaszát a gyorsítótárba helyezi, aztán elküldi a látogatónak. Így ha legközelebb ugyanaz a kérés érkezik, már lesz kész válasz a gyorsítótárban.

Ezt a folyamatot egy egyszerű ábrán is szemléltethetjük:

 

varnish cache folyamat

 

A Varnish telepítése és alap konfigurálása

 

A Varnish legkönnyebben a szervered csomagkezelőjével tudod telepíteni, de ott lehet, hogy nem a legfrissebb verziója lesz elérhető. Ha a legfrissebb verziót szeretnéd, ahhoz is könnyen találhatsz megfelelő bináris csomagokat vagy forgathatsz egyet forráskódból; a hivatalos oldalról letölthető az aktuális verzió. Az egyszerűség kedvéért a példáink csomagkezelő használatát feltételezik, például a Debian 8 („Jessie”) rendszereken elég begépelni ezt:

 

[code]

apt-get install apt-transport-https
curl https://repo.varnish-cache.org/GPG-key.txt | apt-key add -
echo "deb https://repo.varnish-cache.org/debian/ jessie varnish-4.1" >> /etc/apt/sources.list.d/varnish-cache.list
apt-get update
apt-get install varnish

[/code]

Ha forrásból állítod össze, ellenőrizd, hogy a függőségek már jelen legyenek és nézd át a hivatalos dokumentációt is.

A következő lépés a konfiguráció. Először nézzük a konfigurációs fájlt:

/etc/default/varnish (Debian)

/etc/sysconfig/varnish (Red Hat)

 

Jó néhány hasznos és fontos beállítást találunk itt a Varnish daemonra vonatkozóan, mint pl. zárolt megosztott memória, alap TTL, szálak száma stb.

Nézzünk egy egyszerű példát:

[code]

NFILES=131072
MEMLOCK=82000
NPROCS="unlimited"
RELOAD_VCL=1
VARNISH_VCL_CONF=/etc/varnish/default.vcl
VARNISH_LISTEN_PORT=6081
VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1
VARNISH_ADMIN_LISTEN_PORT=6082
VARNISH_SECRET_FILE=/etc/varnish/secret
VARNISH_MIN_THREADS=10
VARNISH_MAX_THREADS=50
VARNISH_THREAD_TIMEOUT=120
VARNISH_STORAGE_SIZE=128M
VARNISH_STORAGE="malloc,${VARNISH_STORAGE_SIZE}"
VARNISH_TTL=120
DAEMON_OPTS="-a ${VARNISH_LISTEN_ADDRESS}:${VARNISH_LISTEN_PORT} \
             -f ${VARNISH_VCL_CONF} \
             -T ${VARNISH_ADMIN_LISTEN_ADDRESS}:${VARNISH_ADMIN_LISTEN_PORT} \
             -t ${VARNISH_TTL} \
             -p esi_syntax=0x2 \
             -p cli_buffer=16384 \
             -p http_resp_hdr_len=131072 \
             -p thread_pools=4 \
             -p thread_pool_add_delay=2 \
             -p thread_pool_min=15 \
             -p thread_pool_max=50 \
             -p thread_pool_timeout=3600 \
             -u varnish -g varnish \
             -S ${VARNISH_SECRET_FILE} \
             -s ${VARNISH_STORAGE}"

[/code]

Ezek a beállítások a Varnish háttérfolyamatára vonatkoznak, a cache-elés folyamatát és a kérések kezelését a VARNISH_VCL_CONF-ban meghatározott .vcl fájl írja le. A cache kezelést egy C-szerű nyelven, a VCL-en írhatjuk meg; ez C-re fordítható és bináris kóddá áll össze; akkor aktiválódik, amikor a kérések beérkeznek. Ennek köszönhetően a Varnish nem csupán cache-elési szempontból tekinthető egy erős HTTP processzornak, használhatjuk még terheléskiegyenlítésre, vagy akár egyszerű tűzfalnak is. A következőkben ezt részletesebben átvesszük.

 

Nézzük meg közelebbről!

 

Vegyük át részletesebben, hogyan működik a kérés feldolgozása a Varnish v4-ben.

Legelőször vessünk egy pillantást a Varnish Finite State Machine ábrájára, amely szemlélteti a folyamatokat:

 

varnish folyamatok

VCL_RECV

Ez a folyamat legeleje, mely közvetlenül a kérés fogadása és elemzése után kerül meghívásra. Meghatározza a további lépéseket a bejövő kérésekkel kapcsolatban: megváltoznak-e, cache-elve lesznek-e vagy sem, lesz-e valami továbbítva a backend-nek feldolgozásra vagy csak egy választ küldünk a gyorsítótárból, stb.?

 

VCL_HASH

A vcl_recv után hívódik meg. A kéréshez egy hash értéket hoz létre, ami a gyórsítótárban tárolt Varnish objektum azonosító kulcsa (lesz); majd a vcl_recv eredményétől függően más függvények felé továbbítja a kérést.

 

VCL_HIT

Akkor kerül meghívásra, ha a gyorsítótárban van a vcl_hash-ben generált azonosítónak megfelelő tartalom. Eredménye meghatározza, hogyan kezeljük a kérés további részét.

 

VCL_MISS

Akkor kerül meghívásra, ha a gyorsítótárban nincs megfelelő tatalom vagy ha a vcl_hit visszautasította/küldte a betöltést.

 

VCL_PASS

A pass mode-ba való belépéskor kerül meghívásra. Ebben a módban a kérés a backend felé továbbítódik, és a backend válasza a klienshez érkezik, de nem kerül bele a cache-be.

 

VCL_PIPE

A pipe mode-ba való belépéskor kerül meghívásra. Ebben a módban a kérés a backendhez kerül, és minden további adat, akár a klienstől, akár a backendtől származik, változatlanul továbbításra kerül, amíg a kapcsolat egyik vége be nem zárul.

 

 

Backend szerverek

A fenti leírásokban számos alkalommal esett szó a backend-ről. A Varnish szaknyelvében a „backend” bármilyen szervert jelenthet, amely a Varnish számára cache-elhető tartalmat nyújt és amelyet a Varnish felgyorsít.

Egy backend kiszolálgó elérését a .vcl fájlban deifniálhatjuk:

 

[code]

backend default {
        .host = "127.0.0.1";
        .port = "8080";
}

[/code]

 

A való életben találkozhatunk szerverösszeomlásokkal és egyéb backend problémákkal. A folyamatos működés biztosításához, meghatározhatunk egy paramétercsoportot (probe néven), amely a backend állapotát ellenőrzi.

[code]

backend default{
        .host = "127.0.0.1";
        .port = "8000";
        .probe = {
                .url = "/alive/";
                .timeout = 2s;
                .interval = 3s;
                .window = 10;
                .threshold = 9;
        }
}

[/code]

 

Ha ezt a paramétercsoportot beállítjuk, akkor a Varnish egy egyszerű GET kérést fog küldeni minden 3. másodpercben az „/alive/” url-nek, 2 másodperces időtúllépési limittel. Mivel az „ablakot” 10-re, a „küszöböt” pedig 9-re állítjuk, a backend egészségesnek minősül, amennyiben az utolsó 10 kérésből legalább 9 sikeresnek bizonyult. A Varnish nem küld adatokat olyan hosztoknak, amelyek nem megfelelő állapotúak.

Így amikor a Varnish-nak tartalomra van szüksége – az alap backend kapcsolatnak köszönhetően – tudja majd, hol találja. De mi a helyzet akkor, ha több backend szerverrel rendelkezünk vagy külön módszerünk van bizonyos kérések kezelésére? Új backendet-et határozhatunk meg (magic néven) és új szabályt adhatunk meg a kérések feldolgozásához:

 

[code]

backend magic {
        .host = "127.0.0.1";
        .port = "8000";
}
...
sub vcl_recv {
        if (req.url ~ "^/magic/") {
                set req.backend_hint = magic;
        } else {
                set req.backend_hint = default;
        }
}

[/code]

 

Szabadon megválasztható, hogy milyen módszerrel határozzuk meg egy kéréshez a backendet – szabályok mentén, paraméterek mentén vagy véletlenszerűen. Ezenkívül több backend szerver is csoportosítható egy „director”-ban. Ehhez be kell tölteni egy VMOD-ot (egy Varnish modult), majd meghívni bizonyos műveleteket a vcl_init-ben.

 

[code]

import directors;    # load the directors
 
backend default {
        .host = "127.0.0.1";
        .port = "8080";
}
backend magic {
        .host = "127.0.0.1";
        .port = "8000";
}
 
sub vcl_init {
        new foobar = directors. round_robin();
        foobar.add_backend(default);
        foobar.add_backend(magic);
}
 
sub vcl_recv {
        set req.backend_hint = foobar.backend();
}

[/code]

Ez a módszer segít, hogy a Varnish-t terhelés-optimalizálásra használhassuk. Itt egy „round-robin director”-t használunk, de létezik egy „random director”, amely véletlenszerűen osztja el a kéréseket.

 

Edge Side Includes (ESI)

Olykor szükség van rá, hogy egyedi információt jelenítsünk meg minden egyes felhasználónak, pl. felhasználónév, kosár tartalma, korábban rendelt termékek listája stb. Ezeknek az adatoknak a cache-elése komoly problémát okozhat, ezért a Varnish-ban alkalmazott ESI blokkok különösen hasznosak.

Az ESI használatával az oldalunkat kisebb részekre oszthatjuk és eldönthetjük, ezekből melyik legyen cache-elve és melyik ne, majd ezekből állíthatjuk össze a választ. Ezeknek a részeknek külön cache beállításai lehetnek. Például, ha van egy listád a webáruházadban lévő 5 legnépszerűbb termékkel, ezt a listát valószínűleg elég egyszer cache-elni és így szerepeltetni más oldalakon, míg más részek esetleg soha nem kerülnek cache-elésre és mindig dinamikusan kell betölteni azokat.

 

varnish esi diagram

 

Ezzel a stratégiával növelheted a találati arányodat és csökkentheted a szerverek terhelését.

Az ESI csupán egy kis részét használja a Varnish teljes funkcionalitásának, ám ez is elegendő. Három ESI utasítást (statements) használhatunk:

  1. esi:include
  2. esi:remove
  3. <!–esi …–>

A Varnish nem dolgozza fel az ESI instrukciókat HTML kommentekben (comments).

Lássunk néhány példát.

 

ESI:include

Az alábbiakban egy egyszerű példát írunk le, ez a user.php fájl:

<?php
…
// Including some libraries
..
$user = $this->getUser();
echo $user->getName();
?>

 

 

Most nézzünk egy HTML fájlt, mely egy ESI include statement-tel rendelkezik. Ez a test.html:

<HTML>
    <BODY>
        Hello <esi:include src="user.php"/>!
    </BODY>
</HTML>

 

Az utolsó lépés az ESI élesítésére az ESI feldolgozás aktiválása VCL-ben.

[code]

sub vcl_backend_response {
        if (bereq.url == "/test.html") {
               set beresp.do_esi = true;
               set beresp.ttl = 24 h;   
        } elseif (bereq.url == "/user.php") {
               set beresp.ttl = 1m;
        }
}

[/code]

 

 

ESI:remove and <!–esi … –>

 

Mit csináljunk, ha az ESI nem áll rendelkezésre, mert nincs mondjuk engedélyezve? Erre az esetre ott vannak az <esi:remove> és <!–esi … –> konstrukciós műveletek. Ezeket bármikor lehet használni tartalmak megjelenítésére, akár él az ESI, akár nem. Például megadhatsz konkrét tartalmat akkor, ha az ESI rendelkezésre áll, és linkelhetsz rá, ha nem.

A “<!–esi” és az ending “-→” tag-eket az ESI feldolgozók eltávolítják az oldal feldolgozásakor, de a köztük levő tartalmat nem. A szintaxis miatt a feldolgozatlan esi tag-ek HTML/XML kommentként működnek.Az <esi:remove> tag-eket az ESI processzorok eltávolítják minden köztük lévő lévő tartalommal együtt.

Például:

<esi:remove>
  <a href="http://www.example.com/LICENSE">The license</a>
</esi:remove>
<!--esi
<p>The full text of the license:</p>
<esi:include src="http://example.com/LICENSE" />
-->

 

Ahogy a péládból is látszik, ezekkel a tagekkel megoldható, hogy dinamikus blokk tartalma jelenjen meg amikor van ESI feldolgozás és egy egyszerű link amikor az ki van kapcsolva.

 

Kik használják a Varnish cache -t?

 

A Wikipédia szerint a világ 10 ezer leglátogatottabb oldalának mintegy 10%-a használja a Varnish-t – hírportálok, közösségi oldalak, médiaoldalak stb. Néhány fontosabb ezek közül: Wikipédia, Facebook, Twitter, Vimeo, The New York Times, The Guardian, The Hindu, Tumblr.

 

A Varnish használata a Magento-nál

 

A Magento a világ legnépszerűbb e-kereskedelmi platformja. Híres rugalmasságáról, de a gyorsaságáról már kevésbé. Ha a Varnish-t Magento 1-hez szeretnéd használni, keress egy külső modult, vagy, ha időd engedi: csináld magad. Ez esetben meg kell tanulnod a Magento használatát, értened kell a HTTP Headerekhez, valamint a fordított proxy cache-eléshez és a Varnish Cache-hez is.

"Csupán két nehéz dolog van a számítástechnikában: a cache érvénytelenítés és az elnevezés."Phil Karlton

A Magento 2 esetében könnyebb a helyzet. A Magento 2 hivatalosan is támogatja a Varnish 3.0.5 és frissebb verzióit és bármely 4.x verziót. Elég, ha követed az útmutató dokumentáció lépéseit, de itt most össze is foglaljuk neked a főbb pontokat:

  • Telepítsd a Varnish-t és teszteld egy tetszőleges Magento oldal megnyitásával, hogy lásd, kapsz-e a válasszal egy HTTP header-t, amely jelzi, hogy a Varnish működik.
  • Telepítsd a Magento szoftvert és a Magento adminisztrációs felületén hozz létre egy Varnish konfigurációs fájlt.
  • Cseréld le a már létező Varnish konfigurációs fájlt az adminban létrehozott fájlra.
  • Tesztelj ismét mindent, és ha semmit sem látsz a /var/page_cache könyvtárban, sikeresen konfiguráltad a Varnish-t a Magento-ban!

Végül érdemes megjegyezni, hogy a saját (vagy ügyfeleidé, ha másoknak fejlesztesz) webáruháza legyen az utolsó hely, ahol az új VCL/PHP kódokat teszteled!

 

 

Drupal Commerce vagy Magento: melyik jobb a vállalkozásod számára?

Az alábbiakban részletesen végigvesszük a két rendszer közötti eltéréseket: milyen funkciókkal rendelkeznek, hogyan használhatóak, mennyire fejleszthetőek és kinek ideálisak?

  • Az alapvető különbségek: tartalom kontra e-kereskedelem
  • Drupal Commerce és ÜberCart
    • Különbségek
    • Amiben hasonlítanak
  • Magento és Drupal: a konkrét különbségek
    • Megjelenés, dizájn
    • Telepítés
    • Adminisztrációs felület
    • Funkciók
    • SEO
    • Skálázás
    • Fejlesztői közösség
  • Melyiket használják a webáruházak?
  • Összegzés – a végső ítélet

 

Mindenekelőtt érdemes tisztáznunk, alapjaiban mennyiben tér el a Drupal és a Magento. A Drupal rendeltetése szerint egy tartalomkezelő rendszer, (Content Management System, vagyis CMS), míg a Magento egy kifejezetten e-kereskedelmi (ecommerce) feladatokra fejlesztett webáruház motor. Mindkét rendszer olyan alapfunkcionalitással bír, amely lehetővé teszi, hogy e szerepükben helytálljanak.

 

Az alapvető különbségek: tartalom kontra e-kereskedelem

 

A Drupal rendszert elsősorban valóban tartalmak közlésére és menedzselésére tervezték : nagyszerűen építhetőek fel rajta blogok, tájékoztató oldalak. Ha egy weboldalon különféle tartalmakat akarunk kínálni, így híreket, e-könyveket, fórumot, közösségi tartalmakat, akkor erre a Drupal remek lehetőségeket kínál. Ahhoz, hogy e-kereskedelmi funkcionalitással is felruházhassuk, a Drupal Commerce-t kell használnunk, a fókusz azonban itt elsősorban a nem hagyományos típusú termékek értékesítésére helyeződik.

Drupal webáruház

Ez alatt azt kell érteni, hogy ha például előfizetős tartalomszolgáltatást akarunk nyújtani, esetleg olyan rendszert kiépíteni, amelyben a különféle előfizetői szinteknek, csomagoknak megfelelően máshoz férnek hozzá a felhasználók, akkor a Drupal maradéktalanul kiszolgálja igényeinket.

Hagyományosabb típusú termékkínálat értékesítésére azonban kevésbé alkalmas a Drupal Commerce, mivel ehhez jó eséllyel komplexebb kategóriarendszerre, illetve számos különféle jellemzővel felruházható terméktörzsre lesz szükségünk.

A Magento motort ellenben kifejezetten a hagyományosabb típusú termékek online értékesítése céljából fejlesztették ki: olyan kis- és nagykereskedelmi célokra egyaránt alkalmazható webáruházat kapunk általa, amely igény szerint természetesen a digitális termékek kereskedelmét is támogatja.

Magento webáruház

A Magento épp azon a téren van hátrányban, ahol a Drupal erősebb: a tartalomszolgáltatásban nem túl erős, kevés opciót kínál.

Ha valaki olyan e-kereskedelmi oldalt szeretne felépíteni, amelyen a tartalomszolgáltatást és az online kereskedelmet egy helyen végezheti, e célra talán a WordPress rendszere a legalkalmasabb, amolyan kompromisszumos megoldásként. A WordPress kezelésének elsajátítása ugyanis meglehetősen egyszerű, a tartalomkezelésben igen erősnek mondható, jól keresőoptimalizálható, a közösségi média kampányokban könnyen integrálható, emellett pedig a WooCommerce révén megfelelő alapfunkcionalitással bíró webáruház modullal is bővíthető (bár ez természetesen egy egyszerűbb megoldás, mint a Magento esetében).

 

Drupal Commerce és ÜberCart

 

A Drupal alapjaira már kétféle e-kereskedelmi modult is választhatunk Ezek közül a régebbi az ÜberCart, az újabb pedig a Drupal Commerce névre hallgat.

 

Az ÜberCart még a legelső ilyen modul volt, amelyet a Drupal 6-hoz készítettek, az évek során természetesen sokat fejlődött funkcionalitásában, működésében, összességében sokkal felhasználóbarátabb lett. A tapasztalatok szerint gond nélkül épül be a Drupal oldalakba, beépített fizetéskezelési (payment processing) lehetőséggel is rendelkezik, a webáruház pedig könnyen telepíthető a segítségével.

 

Különbségek

 

Az ÜberCart egyik gyengesége, hogy content node-ként kezel minden egyes terméket, vagyis minden egyes információ, minden jellemző az adott termékhez kötött, ami okozhat néha hibákat a szállításnál, számlázásnál. Éppen ezért olyan termékek esetében egyszerűbb használni, amelyeknél nem kell variációkat megadni: ilyenek lehetnek például a könyvek. Amennyiben például egy nadrágot akarunk bevinni, amely több méretben és színben is kapható, úgy a „nadrág” node alá kerülnek be a különféle variációk, a rendszer felépítése miatt pedig ez nagyszámú terméknél már zavaró lehet.

A Drupal Commerce esetében ellenben minden egyes termékvariáció önálló entitásként van jelen, nem pedig az eredeti termék elágazásaiként. Ahhoz, hogy a különféle változatokat egy helyen jelenítsük meg (tehát az adott nadrágtípus oldalán az összes színt és méretét például) létre kell hoznunk egy külön csomópontot (ez lesz a display node). Az ÜberCartnál bonyolultabb alapokon nyugvó rendszer ez, azonban elméletben közelebb áll a hagyományos terméknyilvántartásokhoz.

A Drupal Commerce-nek nem része a fizetési rendszer, ezen funkciók használatához további modulokat kell telepítenünk hozzá – az ÜberCart esetében ugyanakkor a fizetés és szállítás modulok is opcionálisan alapból telepíthetőek.

 

Amiben hasonlítanak

 

Mind a két változat telepítése viszonylag egyszerű, az alapbeállítások használatával egy jól funkcionáló webshopot kapunk. Az adminisztráció összességében nem tér el nagyban, legalábbis a kezelőfelületet tekintve, illetve a webboltok teljes mértékben testreszabhatóak. A Drupal Commerce kezelésének elsajátítása ugyan tovább tart, de az ÜberCart és az újabb Drupal kereskedelmi modul egyaránt viszonylag egyszerűen kitanulható.

 

Magento és Drupal: a konkrét különbségek

 

Az egyszerűség kedvéért a következőkben a két Drupal modul közül az újabb, többek által használt változat, a Drupal Commerce képességeit hasonlítjuk össze a Magento CE, vagyis a Magento rendszer ingyenes, nyílt forráskódú változatának képességeivel.

Drupal Commerce funkcionalitás

Telepítés

 

Az első jelentősebb különbség már a telepítésnél megmutatkozik. A Magento telepítése rendkívül egyszerű folyamat, körülbelül tíz perc alatt elvégezhető – a Magento esetében komplexitással a későbbiekben találkozunk csak, egy webáruház létrehozása szinte már játszi könnyedséggel megy.

A Drupalnál viszont az alapfokú ismeretekkel rendelkező felhasználó már belefuthat problémákba. A hivatalos oldalon egy külön Commerce Kickstart applikációt kínálnak, amely lehetővé teszi, hogy „kihagyjunk több hétnyi konfigurációt, amíg a Drupal Commerce keretrendszerben felépítjük ecommerce oldalunkat”.

A Kickstart viszonylag gyorsan végez, így elméletileg egy teljesen működőképes próbaboltot (demo store) kapunk témával, katalógussal, kereséssel és back office interfésszel, viszont értelemszerűen a finomhangolás nélkül – ha viszont első webáruházat építünk, akkor ez nem jelent nagy problémát.

 

Adminisztrációs felület

 

A Magento interfésze egy jól kiforrott, sokat próbált felület, amelyet alapvetően igen könnyű kiismerni, ami magát a kezelést illeti. Az olyan felhasználók számára azonban, akik a Magento használatában kezdők, zavaró lehet a rengeteg különféle opció: nem könnyű eligazodni a rengeteg menü, opció, beállítási lehetőség között, mindenképpen időt igényel a tanulás.

Online viszont számos tréningvideó, leírás, kurzus érhető el, amely segíthet abban, hogy az alapok használatát ösztönössé tegyük – a későbbiek során pedig elsajátíthatjuk, hogyan működnek a rendszer azon részei, amelyek már a komplexebb beállításokat rejtik.

A Drupal esetében sem öt percbe telik megtanulni, pontosan mi hogyan működik és hol is találjuk a fontos dolgokat, ugyanakkor a tartalomkezelő rendszer újabb verziói már jóval könnyebben kezelhetőek elődjeiknél. Előnyt jelenthet a kezelésben az is, hogy az adminisztrációs modult megjeleníthetjük a weboldalon is, így gyorsabban módosíthatunk bizonyos dolgokat.

 

Funkciók

 

Mint eleve webáruháznak tervezett rendszer, a Magento kínál olyan plusz funkciókat, amelyeket a Drupal esetén hiába keresnénk. Ezek közül néhányat az alábbiakban részletezünk.

A Magento nagy erőssége más e-kereskedelmi platformokkal szemben, hogy lehetőség van a szűkítő keresésre, ami a felhasználóknak kényelmes és hatékony módot kíván arra, hogy az igényeiknek leginkább megfelelő terméket megtalálják akár több ezer termék között is.

A szűkítő kereséssel kereshetünk kategóriák, árkategória, brand, szín vagy bármely más változó alapján. Ez azért is különösen fontos, mert a Magento egyedülálló lehetőségeket ad arra, hogy a termékek jellemzőit testre szabjuk az adatbázisban, gyakorlatilag bármilyen változót megadhatunk, így a webáruházat akkor használhatják igazán kényelmesen a felhasználók, ha lehetőségük van mindenre rákeresni. Ez tehát szintén a nagy darabszámmal és változatos termékekkel működő webáruházak számára biztosít előnyt.

A Magento rengeteg beépített promóciós lehetőséget is kínál, amelyek ideálisak arra, hogy a látogatókat konvertáljuk, hogy az átlagos vásárlási értéket növeljük, csökkentsük a kosárelhagyók arányát vagy éppen újra aktiváljuk régebbi vásárlóinkat.

magento CMS-t használó brandek

Kuponos akciókat indíthatunk, egyedi kedvezményeket kínálhatunk bizonyos termékcsoportokra vagy akár felhasználók adott csoportjainak (például a visszatérő vásárlóknak kedvezményt adhatunk), lehetőségünk van cross- és upsell akciókra és így tovább.

Termékajánló modult is beépíthetünk, hogy ez utóbbi lehetőségeket a legjobban ki tudjuk használni. Elhelyezhetünk CTA-kat, amelyek a vásárlás egy adott pontján arra ösztönzik a vásárlót, hogy az adott termékből többet/nagyobbat rendeljen (pl. takarítson meg azzal, hogy egy termékből előre megrendeli a szükséges mennyiséget, akár kedvezményt is adhatunk erre), vagy hogy egy olyan másik termékkel együtt tegye a kosárba, amelyet mások gyakran párosítanak hozzá (pl. egy laptophoz plusz tárhelyet ajánlhatunk).

A Community Edition rendszer hűségrendszert, hűségpont-kezelést alapvetően nem tartalmaz, vásárolt vagy fejlesztett modulként azonban bővíthetjük ilyen funkcionalitással is a webáruházat. Összetett kampányokat futtathatunk akár e-maileket is küldve, felhasználói csoportokat alakítva ki, adott az eszközkészlet arra, hogy egy felhasználó vásárlóélettartam-értékét a lehető legnagyobbra növeljük.

 

Megjelenés, dizájn

 

A Drupal igen gazdag testreszabási lehetőségekkel rendelkezik, ami a megjelenését illeti, és a rendelkezésre álló témákon belül is sok mindent módosíthatunk, viszonylag könnyen teljesen egyedivé tehetjük az oldalunkat.

drupal commerce áruházak

A Magento esetében is rendelkezésre állnak ingyenes és fizetős dizájnok is, a legjobban viszont természetesen akkor járunk, ha teljesen saját megjelenést adunk a webboltnak. Ebben az esetben, ha kicsit nagyobb költségvetéssel rendelkezünk, akkor igazán egyedi és különleges megjelenítést is létrehozhatunk a megfelelő szakemberek segítségével. A Magento rendszere azonban igen bonyolult és kényes, így nem ajánlott, hogy amatőrként bárki elkezdjen kísérletezgetni komolyabb átszabásával.

 

SEO

 

Alapvetően mind a két rendszer jól keresőoptimalizálható, e téren azonban a Drupalnak előnye van tekintettel arra, hogy eleve tartalomkezelő rendszerként működik. Ennek ellenére a Magento esetében sem kell lemondanunk arról, hogy jól rangsoroljunk a találati oldalakon, a rendszer összességében igen jól optimalizált, ugyanakkor egyes témák esetében a tapasztalatok szerint találkozhatunk nehézségekkel (ezért is lehet praktikusabb, ugyanakkor természetesen drágább és időigényesebb szakemberek segítségével saját fejlesztéseket használni).

 

Skálázás

 

Ebben a körben egyértelműen a Magentónál az előny. A sok terméket, illetve azok különféle változatait áruló webáruházak nem véletlenül épülnek inkább erre a rendszerre, és ritkán Drupalra. Ahogyan arról beszéltünk is, a Drupal erőssége a tartalom, és igazán akkor ideális, ha kisszámú és kevés különböző jellemzővel rendelkező terméket akarunk eladni, így könyveket például.

A Magento gyakorlatilag gond nélkül képes sok ezer terméket kezelni, rengeteg különféle (akár teljesen egyedi) attribútumot rendelhetünk ezekhez, amelyek könnyen kereshetőek is. Ha tehát nagyra törünk, és egy nagyvállalat egész termékskáláját akarjuk értékesíteni, vagy olyan áruházat építünk, ahol eleve nagyszámú terméket értékesítünk, akkor a Magento felé billen a mérleg nyelve.

 

Fejlesztői közösség

 

Mindkét rendszer mögött erős és aktív nemzetközi közösség áll, akik kreatív fejlesztéseket végeznek rajtuk. E fejlesztések iránya azonban alapvetően más: míg a Magento esetében az ecommerce funkcionalitás áll a középpontban, addig a Drupal rendszerrel foglalkozók jellemzően a CMS funkciókat bővítik, finomítják vagy gondolják újra – tehát az e-kereskedelmi modulokkal aránylag kevesen foglalkoznak.

 

Melyiket használják a webáruházak?

 

A Magento évek óta az egyik legnépszerűbb webáruházmotor, részesedését az e-kereskedelemben a felmérések szerint csak a WordPress rendszerű WooCommerce képes felülmúlni. A nagyobb látogatottságú oldalak, illetve a nagyvállalatok leginkább a Magento rendszerét használják.

Világszinten a legfrissebb adatok szerint körülbelül az összes webáruház negyede használja a Magentót, a Drupal Commerce és ÜberCart részesedése ezzel szemben szinte mérhetetlen – a legtöbb felmérésben nem is szerepel, vagy az „egyéb” kategóriába sorolják.

 

ÖSSZEGZÉS

Ha olyan weboldalt akarunk építeni, ahol elsősorban a tartalommarketing eszközeivel népszerűsítjük kisebb számú termékünket, akkor a tartalomkezelő rendszerként működő Drupal jobb megoldás lehet. Ha pedig a tartalom másodlagos, így például egy blogot akarunk csak vezetni az oldalon, miközben komoly termékskálát kínálunk és komplexebb webáruház-funkcionalitásra is szükségünk van, akkor a Magento optimálisabb választás.

drupal nem főként e-kereskedelemre

Induló e-kereskedelmi vállalkozásokra a Drupal javasolt inkább, mert egyszerűbb és nem utolsósorban olcsóbb testre szabni, – bár fejlesztői tapasztalt nélkül szintén nem javasolt komolyabban átalakítani, a Magento esetében viszont biztosan fejlesztői segítségre lesz majd szükségünk.

Nagyvállalatok, a termékeiket netre költöztető komolyabb kereskedelmi láncok, tehát a komolyabb igényekkel rendelkezők viszont úgy tapasztalják majd, hogy megéri befektetni egy megfelelően optimalizált Magento oldal létrehozásába.

 

 

Hány látogatót tud kiszolgálni a webáruházad? Teszteld!

Mi is az a teljesítmény tesztelés?

Az ISTBQ (International Software Testing and Qualification Board) szerint a teljesítmény tesztelés nem más, mint a “Tesztelési folyamat, mellyel a szoftvertermék teljesítményét lehet meghatározni.”

A Wikipédia szerint: Tesztelési folyamat, amely meghatározza, hogy milyen gyors a rendszer egy bizonyos szempontból, adott terhelés alatt. Ezenkívül validálhat és verifikálhat más minőségi tényezőket is, mint skálázhatóságot, megbízhatóságot vagy forrás kihasználtságot.

Tehát egy olyan nem funkcionális tesztelési folyamat, amely az alkalmazás (szoftver, azaz Magento webáruház) teljesítményével foglalkozik és a fejlesztés teljes szakaszában használható a problémák felfedésére és megelőzésére.

Szerintem a Wikipédia megállapítása áll legközelebb a meghatározáshoz, azonban pontos definíciót adni, mint sok más esetben, úgy itt sem lehet.

 

Miért van szükséged teljesítmény tesztelésre?

Az internet térhódításával egyre többen élnek az online vásárlás lehetőségével, így egyre többen érik el a webáruházakat is. Manapság már az emberek többsége – ahelyett, hogy a boltba menne és kipróbálná a terméket az eladótól segítséget kérve – már szinte igazi szakértőként összehasonlít, vizsgál, keresgél a világ minden webáruházának termékeiből, hogy számára a legmegfelelőbbet találja meg. Egy ünnep vagy nagy akció (mint például a Karácsony vagy a Fekete Péntek) közeledtével pedig a boltok helyett a web világában rohanják meg a vásárlók áruházunkat.

Ezeket a megnövekedett terheléseket vagy a folyamatos, erős forgalmat áruházunknak ki kell szolgálnia, ezért is szükséges a teljesítmény tesztelése.

A webáruházat több komponens alkotja, van szoftveres, hardveres és hálózati keresztmetszet része is, melyek további szegmensekre bonthatók egészen a használt keretrendszer, a szerver processzorának teljesítménye vagy a letöltött oldal kódjának adat mennyiségéig. Ha ezekre nem figyelünk oda, akkor egy-egy kiugró terhelés esetén a webáruházunk lelassulhat, elérhetetlenné válhat vagy a legrosszabb esetben teljesen össze is omolhat.

 

A teljesítmény tesztelés fajtái

Az alábbi felsorolásban ismerjük meg a releváns teljesítmény tesztelés fajtáit, a teljesség igénye nélkül, kiemelve a webáruházakon alkalmazott legfontosabb teszteléseket. Részletesebb információért a Wikipédia szoftver tesztelés leírását tudom ajánlani.

1. Terheléses tesztelés (Load testing): A legegyszerűbb teszt, mivel itt azt verifikáljuk, hogy az alkalmazás hogyan viselkedik normális vagy magasabb terhelés alatt, változik-e a viselkedése mindeközben.

   a) Tűrőképesség teszt (Endurance testing): Hosszabb ideig tartó, folyamatos terhelés mellett vizsgálja a rendszer működését, így az egy-két órás tesztek alatt rejtve maradó problémákat az akár több napos teszt segíthet a felszínre hozni.

2. Stressz tesztelés (Stress testing): Ideális esetben arra használjuk, hogy megismerjük a rendszer felső határait, a töréspontokat. Ez a teszt típus kész meghatározni a rendszer robusztusságát extrém terhelés közben, és segíti az adminisztrátorokat, hogy meghatározzák a rendszer ideális működési tartományát és maximumát.

   a) Kapacitás teszt (Capacity testing): A Stressz tesztelés folyamán megállapítjuk, hogy mennyi felhasználót / lekérdezést / műveletet tud egyszerre végrehajtani hibamentesen a rendszer.

3. Kitartás tesztelés (Soak testing): A normális működést szimulálva a kitartás teszt egy olyan működés, ami képes meghatározni, hogy a folyamatos használati terhelését mennyire bírja a rendszer. A teszt alatt folyamatosan figyelni kell a memóriát és ezzel felderíthetők a memória szivárgási problémák.

4. Tüske tesztelés (Spike testing): Hirtelen megugrott terhelést adunk a rendszernek, majd ezt ugyanilyen hirtelen lecsökkentjük. Ez a gyors változtatás „tüskeként” jelenik meg a rendszer terhelésének grafikonjában, innen kapta a teszt a nevét.

5. Konfigurációs tesztelés (Configuration testing): Felmerülhet a kérdés, hogy ez a teszt mit keres a teljesítmény tesztek között. Mivel ez a teszt azt vizsgálja, hogy a konfigurációs beállítások megváltoztatásával milyen hatással van a rendszer részeire vagy egészére, elsősorban a teljesítményére, ezért az itteni említése jogos.

6. Izolációs tesztelés (Isolation testing): A teljesítmény tesztelésnek nem egy egyedi típusáról van szó, inkább egy ismétlődő tesztelésről, ami fényt derít egy rendszerproblémára. A tesztek gyakran külön környezetekbe izoláltak és a teszt eredményeként csak a hibás környezetet kapjuk eredményül.

teljesitmény teszt PET ábra

A teljesítmény tesztelési típusok fajtái

A tesztelés lépései

      1. Tesztkörnyezet meghatározása (létrehozása, kialakítása)
      2. Elfogadási kritérium(ok) meghatározása
      3. Teszt(ek) megtervezése (tesztelési forgatókönyv megírása)
      4. Tesztkörnyezet konfigurálása (adatfeltöltés, paraméterek beállítása)
      5. Tesztek implementálása
      6. Tesztek futtatás
      7. Eredmények kiértékelése, riportok készítése, újrafuttatás

Tsung teljesítmény teszt logó

 

Tsung (IDX-Tsunami 1.6.0)

A Tsung egy megosztott terheléses teszt eszköz. Protokoll függő és jelenleg a következő protokollal kommunikáló szervereken futtatható:

A Tsung fő erőssége, hogy képes szimulálni egyetlen gépről nagy mennyiségű felhasználót. Ha több gépen (klaszter) használjuk, akkor pedig igazán lenyűgöző teljesítményt tud produkálni, mindezt könnyen konfigurálhatjuk és fenntarthatjuk.

Tsung működése ábra, teljesítmény teszt

A Tsung működésének elvi vázlata

 

Jellemzők

  • Magas teljesítmény
  • Elosztott működés
  • Több protokoll támogatás
  • SSL támogatás
  • Különböző IP címek kiosztása azonos gépről
  • Operációs rendszer monitorozása a tesztelés alatt
  • XML konfigurációs rendszer
  • Dinamikus forgatókönyvek (tranzakciók)
  • Kevert viselkedés a felhasználóknak (munkamenetek)
  • Sztohasztikus feldolgozás (thinktime)

 

Mi az az Erlang és miért fontos?

A Tsung-ot Erlang nyelven fejlesztették és ez az, ami olyan erőssé teszi, mivel az Erlang egy párhuzamosság-orinetált programozási nyelv (concurrent programming). Az Erlang OTP (Open Transaction Platform) képezi a Tsung alapját, ami így a következő főbb jellemzőkkel ruházza fel:

      • Teljesítmény
      • Skálázhatóság
      • Hibatűrő képesség

 

Protokollok és teljesítmény

A Tsung nagy teljesítményre képes, ha a megfelelő hátteret biztosítjuk a számára. Számokban ez az alábbiakat jelenti:

      • Jabber/XMPP protokoll:
        • 90,000 szimulált JABBER felhasználó egy 4-es Tsung klaszteren.
        • 10,000 szimulált felhasználó: Tsung 3 számítógépből (800MHz-es CPU) álló klaszteren.

 

      • HTTP és HTTPS protokoll:
        • 12,000 szimulált felhasználó: Tsung 4 számítógépből álló klaszteren (2003) 3000 lekérés/másodperc
        • 10,000,000 szimulált felhasználó: Tsung 75 számítógépből álló klaszteren több, mint 1,000,000 lekérés/másodperc

 

A Tsung használata

 

A Tsung teljesítmény tesztelés végrehajtásához először is fel kell telepítenünk azt a szerverünkre, melyhez az Amazon EC2 – Virtual Server Hosting szolgáltatása az egyik legkézenfekvőbb és kényelmes szolgáltatás.

A Tsung telepítése

A Tsung telepítését kísérjük most végig lépésről lépésre, hogy mindenki számára könnyen érthető legyen. A VPS szerver konfiguráció, amire telepítésre kerül, a következő:

      • CentOS 6.7 opercáiós rendszer
      • CPU: 8 magos (Intel(R) Xeon(R) CPU E5-2630 v2 @ 2.60GHz)
      • Memória: 3 GB
      • Tárhely: 10 GB HDD

 

      1. Első lépésként az Erlang-ot kell telepíteni, valamint a Firefox-ot is, mivel azon keresztül történik a jelentések generálása (és megnyitása, ha GUI-t használunk).

 


[root@aion-tsung ~]# yum -y install erlang perl perl-RRD-Simple.noarch perl-Log-Log4perl-RRDs.noarch gnuplot perl-Template-Toolkit firefox

 

      1. Ezt követően töltsük le a Tsung-ot és installáljuk:

 


[root@aion-tsung ~]# wget http://tsung.erlang-projects.org/dist/tsung-1.6.0.tar.gz
[root@aion-tsung ~]# tar zxfv tsung-1.6.0.tar.gz
[root@aion-tsung ~]# cd tsung-1.6.0
[root@aion-tsung ~]# ./configure && make && make install

 

      1. A Tsung jelentés elkészítésének parancsára hozzunk létre egy pre-set parancsot (alias command) a könnyebb használat érdekében (vim használatával):

 


[root@aion-tsung ~]# vim ~/.bashrc
vim > alias treport="/usr/lib/tsung/bin/tsung_stats.pl; firefox report.html"
vim > :w
vim > :q
[root@aion-tsung ~]# source ~/.bashrc

 

      1. Készítsük elő az első felhasználáshoz a Tsungot (opcionális):

 


[root@aion-tsung ~]# cd /root/
[root@aion-tsung ~]# mkdir .tsung
[root@aion-tsung ~]# cd ..
[root@aion-tsung ~]# cp /usr/share/doc/tsung/examples/http_simple.xml /root/.tsung/tsung.xml

 

A Tsung konfigurálása (/root/.tsung/tsung.xml)

A Tsung XML konfigurációs állományának első szintje meglehetősen kötött, azonban a munkamenetek és a tranzakciók használatával jól irányíthatóak a folyamatok és a monitorozás. Nézzük meg példának az examples/http_simple.xml állományt:

<?xml version="1.0"?>
<!DOCTYPE tsung SYSTEM "/usr/share/tsung/tsung-1.0.dtd">
<tsung loglevel="notice" version="1.0">

    <!-- Client side setup -->
    <clients>
        <client host="localhost" use_controller_vm="true"/>
    </clients>

    <!-- Server side setup -->
    <servers>
        <server host="195.56.150.103" port="80" type="tcp"></server>
    </servers>

    <!-- to start os monitoring (cpu, network, memory) -->
    <monitoring>
        <monitor host="195.56.150.103" type="snmp"></monitor>
    </monitoring>

    <load>
        <!-- several arrival phases can be set: for each phase, you can set
        the mean inter-arrival time between new clients and the phase
        duration -->
        <arrivalphase phase="1" duration="10" unit="minute">
            <users interarrival="2" unit="second"></users>
        </arrivalphase>
    </load>

    <options>
        <option type="ts_http" name="user_agent">
            <user_agent probability="80">Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.8) Gecko/20050513 Galeon/1.3.21</user_agent>
            <user_agent probability="20">Mozilla/5.0 (Windows; U; Windows NT 5.2; fr-FR; rv:1.7.8) Gecko/20050511 Firefox/1.0.4</user_agent>
        </option>
    </options>

    <!-- start a session for a http user. the probability is the
    frequency of this type os session. The sum of all session's
    probabilities must be 100 -->

    <sessions>
        <session name="http-example" probability="100" type="ts_http">

            <!-- full url with server name, this overrides the "server" config value -->
            <request>
                <http url="/" method="GET" version="1.1"></http>
            </request>
            <request>
                <http url="/images/accueil1.gif" method="GET" version="1.1" if_modified_since="Fri, 14 Nov 2003 02:43:31 GMT"></http>
            </request>
            <request>
                <http url="/images/accueil2.gif" method="GET" version="1.1" if_modified_since="Fri, 14 Nov 2003 02:43:31 GMT"></http>
            </request>
            <request>
                <http url="/images/accueil3.gif" method="GET" version="1.1" if_modified_since="Fri, 14 Nov 2003 02:43:31 GMT"></http>
            </request>

            <thinktime value="20" random="true"></thinktime>

            <request>
                <http url="/index.en.html" method="GET" version="1.1"></http>
            </request>

        </session>
    </sessions>
</tsung>

A fenti példában a szerverre telepített tsung a localhost-ról indítja a klienseket, melyek célja a 195.56.150.103 ip címen lévő szerver 80-as portja. A szerver monitorozására az SNMP protokollt használjuk és egy tsung klienst. A <load> szakaszban látható, hogy egy szakaszból áll a teszt, ami 10 percig fut és 2 másodpercenként hoz létre egy felhasználót. Az <options> szakaszban megadott böngészővel azonosítják magukat a létrehozott felhasználók 20-80%-os arányban.   A <sessions> munkameneteket leíró szakasz határozza meg a felhasználók interakcióját, mely a következő lépésekből áll:

      1. A 195.56.150.103 kezdő oldal elérése (HTTP GET request)
      2. A 195.56.150.103/images/accueil1.gif kép elérésnek ellenőrzése, ha azt a megadott időpont óta módosították.
      3. A 195.56.150.103/images/accueil2.gif kép elérésnek ellenőrzése, ha azt a megadott időpont óta módosították.
      4. A 195.56.150.103/images/accueil3.gif kép elérésnek ellenőrzése, ha azt a megadott időpont óta módosították.
      5. A felhasználó ezután 0-20 másodperc közötti véletlen időtartamig várakozik.
      6. A 195.56.150.103/index.en.html oldal betöltése.

 

Tsung konfigurációs lehetőségek

Az előző példában bemutatott XML mellett további lehetőségeink vannak még, melyekkel még jobban személyre szabhatjuk teljesítmény tesztjeinket. Az alábbi lista a teljesség igénye nélkül készült, részletekért keresd fel a Tsung konfigurációs XML dokumentációját:

      • Felhasználók felső határának korlátja (maxusers)
      • Dinamikusan és statikusan létrehozható felhasználók
      • Szakaszok maximális futási idejének meghatározása
      • Felhasználók „gondolkozási idejének” beállítása, véletlenszerűség, hibernálás
      • Kapcsolat időtúllépésének megadása
      • Ismételt próbálkozások száma, ha a kapcsolat nem épült fel
      • HTTP, LDAP authentikáció lehetősége
      • MySQL lekérdezések futtatása
      • Variálható munkamenet típusok
      • Külső (CSV) állományok betöltése és feldolgozása
      • Dinamikus változók használata (JSONPath, Regexp, XPath, PostgreSQL, dinmaikus változók)
      • Iterációk megvalósítása (for, repeat, if, foreach)

 

A Tsung paraméterezése és futtatása

A Tsung futtatása meglehetősen egyszerű, azonban mindenképpen a Screen alkalmazás használatával javasolt, hogy ha a VPS-sel elveszne a kapcsolat, akkor is tovább fusson a teszt. Nézzük meg a beépített helpert, ami megfelelően dokumentált, így külön magyarázatot nem is fűznék hozzá:


[root@aion-tsung ~]# $ tsung -h
Usage: tsung <options> start|stop|debug|status
 Options:
   -f <file>     set configuration file (default is ~/.tsung/tsung.xml) (use - for standard input)
   -l <logdir>   set log directory (default is ~/.tsung/log/YYYYMMDD-HHMM/)
   -i <id>       set controller id (default is empty)
   -r <command>  set remote connector (default is ssh)
   -s                  enable erlang smp on client nodes
   -p <max>      set maximum erlang processes per vm (default is 250000)
   -m <file>     write monitoring output on this file (default is tsung.log) (use - for standard output)
   -F                  use long names (FQDN) for erlang nodes
   -w                  warmup delay (default is 10 sec)
   -v                  print version information and exit
   -6                  use IPv6 for Tsung internal communications
   -h                  display this help and exit

Tsung futtatása a /root/.tsung/ könyvtárból, ha a konfigurációs állomány a simple_website_check.xml:


[root@aion-tsung ~]# screen tsung -f simple_website_check.xml start

A parancs hatására egy külön taszkban fog futni a tsung, amennyiben az xml konfigurációs állomány megfelelő szintaktikájú. A validálást a futtatás előtt megteszi a Tsung, ha hibát talál akkor hibakeresési információkkal megszakítja a futtatást. A teszt alatt keletkező napló állományok alapértelmezetten a ~/.tsung/log/YYYYMMDD-HHMM könyvtárba kerülnek, de ez könyvtár a -l paraméterrel módosítható.

 

Futtatás közben

Az 1.6-os verziótól kezdve közvetlen webes monitorozási lehetőséget (dashboard) kapunk a teszt futása alatt, ami nagy segítség abban, hogy lássuk hogyan alakul a teszt folyamata. Így lehetőségünk van még időben beavatkozni, hogyha esetleg a teszt szélsőséges eredményeket hozna vagy a rendszer összeomolna.

A dashboard a Tsung szerveren érhető el a 8091-es porton keresztül az alábbi paraméterezéssel:

{tsung szerver domain/ip}:8091/es/ts_web:status

 

Tsung konzol

A konzolban nem sok információt kapunk vissza a tesztről

Tsung dashboard státusz

Tsung Dashboard működés közben: {domain}:8091/es/ts_web:status

 

Futtatás után: jelentés készítése

Ha a tesztünk lefutott, akkor a naplózáshoz megadott vagy az alapértelmezett (vagy a paraméterként megadott) mappában megtaláljuk a napló (.log) állományokat, az XML konfigurációs állományunk és a csatolt csv-k másolatát is. A mappába belépve kiadva a treport parancsot – amit korábban létrehoztunk – és ezzel legeneráljuk a teszt eredményeihez tartozó HTML jelentést, melyet böngészőben a report.html file megnyitásával tekinthetünk meg.   A jelentés állomány készítése előtt csak a napló állományok és a dashboard HTML oldala található a naplózáshoz kijelölt mappában:

Tsung naplózási mappa

A naplózási mappa tartalma a teszt lefutása után

Tsung treport futtatása

A treport futtatása konzolból

Tsung naplózási mappa teljesítmény teszt

A jelentés készítés után a naplózási mappa tartalma

 

Jelentés felépítése

Az elkészített jelentést egyszerűen böngészővel nyissuk meg és megkapjuk számszerűsítve a teljesítmény teszt eredményét. A jelentés megfelelően részletes ahhoz, hogy meghatározzuk a rendszer töréspontjait, az esetlegesen nem megfelelő folyamatokat vagy azok gyenge pontjait és ezek alapján kód refaktorálással, szerver skálázással vagy fejlesztéssel megerősítsük/felgyorsítsuk az alkalmazásunkat.

Tsung jelentés statisztika

A jelentés statisztikája

 

A bal oldali menü két nagyobb szakaszra bomlik a felső részben vannak a számszerűsített statisztikák:

 

Main Statistics

      • Transactions: A tranzakciók összefoglalása
      • Network Throughput: A hálózat áteresztő képessége (sebesség/adatmennyiség)
      • Counters: Ferlhasználók, sikeres csatlakozás, lefutott fázisok, stb.
      • Server monitoring: A monitorozás eredménye
      • HTTP status: A HTTP állapot kódok
      • Errors: A futás folyamán fellépő hibák

A második szakaszban az egyes grafikonokat nézhetjük meg, azonban ezt én még meglehetősen kezdetlegesnek érzem.

Graphs

      • Response times: Válaszidők változása a teszt alatt.
      • Throughput graphs: A hálózat terhelésének változása a teszt alatt.
      • Simultaneous Users: A teszt alatt szimulált felhasználók viselkedése.
      • Server monitoring: Az opercáiós rendszer CPU, memória terhelése
      • HTTP status: A válasz HTTP kódok a tesztelés alatt.
      • Errors: A kapott hibák a tesztelés alatt.

 

Tsung jelentés grafikonok

A Tsung jelentés grafikonjai

 

ÖSSZEFOGLALÁS

A teljesítmény tesztelés kiemelten fontos az e-kereskedelmi megoldásoknál, főleg a Magento webáruházaknál, hiszen a felhasználók gyorsan, akadály- és hibamentesen akarnak eljutni a vásárlás végéig és megkapni termékeiket. Főleg ünnepnapok, akciók és jól célzott marketingaktivitás közben és után számítanunk kell komoly terhelésre, így azoknak, akik szeretnék a vásárlóikat megtartani és nem arról híressé/hírhedtté válni, hogy a webáruházuk elérhetetlen, erősen javasolt a teljesítmény tesztelés.

A Tsung tökéletesen alkalmas eszköz arra, hogy teljesítmény teszteket végezzünk, akár az egész áruházra, akár az egyes folyamatokra nézve (adatbázis terhelés, fizetés, stb.), mindezt a legismertebb és leginkább használt protokollok támogatása mellett. Könnyű konfigurálása miatt ideális, hogy rövid tanulással is komoly, minden tekintetben profi teszteket futtassunk. Az automatikus jelentés készítésével pedig mindezt – grafikonokkal színesítve – „laikus” emberek számára is érthető formába önthetjük.

 

Magento 2 modul fejlesztés lépésről lépésre – 2. rész

Ebben a cikkben a következő témákat öleljük fel:

1) Admin menüpont és táblázat (grid) elkészítése

Első lépésben létrehozunk az adminisztrációs felületen a modulunkhoz tartozó menüpontot. Ezt külön (új) főmenüben is elhelyezhetjük, de célszerű a modul működéséhez igazodva besorolni a már létező főmenüpontok alá. Jelen esetben a Content főmenüpontban helyezzük el a sajátunkat. Ehhez szükségünk lesz egy új file-ra. A menüpontot az app/code/Aion/Test/etc/adminhtml/ könyvtárban lévő menu.xml-ben valósítjuk meg. A fájl tartalma:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
    <menu>
        <add id="Aion_Test::content_elements" title="Test Extension" module="Magento_Backend" sortOrder="10" parent="Magento_Backend::content"
             resource="Magento_Backend::content_elements"/>
        <add id="Aion_Test::aion_test" title="Manage Items" module="Aion_Test" sortOrder="10" parent="Aion_Test::content_elements" action="test/test"
             resource="Aion_Test::test"/>
    </menu>
</config>

A file-ban első lépésben definiálunk egy főelemet, ami nem más, mint az id=Aion_Test::content_elements és a menüpontot ez alá helyezzük el, úgy hogy a menüpont (id=Aion_Test::aion_test) parent-nél a főelemet adjuk meg. Ha mindent jól csináltunk, az adminisztrációs felületen a Content főmenüpontban megjelenik a saját almenüpontunk is. A menüpontnál még fontos megemlíteni az Action=”test/test” paramétert, ami a később kialakítandó adminhtml controller útvonalát hivatott megadni.   A következő lépésben szükséges elkészíteni az adminhtml controllert és layout file-t, ami a grid megjelenítésért felelős. Azonban előtte csinálunk egy abstract controller osztályt, hogy a backend jogosultság kezelést egy helyen valósítsuk meg.   Az abstract controller osztályt az app/code/Aion/Test/Controller/Adminhtml/ könyvtárban lévő Test.php-ban valósítjuk meg. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Controller\Adminhtml;

/**
 * Aion manage items controller
 */
abstract class Test extends \Magento\Backend\App\Action
{
    /**
     * Core registry
     *
     * @var \Magento\Framework\Registry
     */
    protected $_coreRegistry = null;

    /**
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Framework\Registry $coreRegistry
     */
    public function __construct(\Magento\Backend\App\Action\Context $context, \Magento\Framework\Registry $coreRegistry)
    {
        $this->_coreRegistry = $coreRegistry;
        parent::__construct($context);
    }

    /**
     * Init page
     *
     * @param \Magento\Backend\Model\View\Result\Page $resultPage
     * @return \Magento\Backend\Model\View\Result\Page
     */
    protected function initPage($resultPage)
    {
        $resultPage->setActiveMenu('Aion_Test::aion_test')
            ->addBreadcrumb(__('Test'), __('Test'))
            ->addBreadcrumb(__('Items'), __(''));
        return $resultPage;
    }

    /**
     * Check the permission to run it
     *
     * @return boolean
     */
    protected function _isAllowed()
    {
        return $this->_authorization->isAllowed('Aion_Test::test_menu');
    }
}

Az abstract controller initPage() függvénye (metódusa) felelős az aktív menüpont beállítása mellett a breadcrumb (morzsa menü) útvonal beállításáért is. A másik fontos függvény az _isAllowed(), ami az adminisztrátor jogosultságot ellenőrzi és kezeli.   Ezután elkészítjük az admin táblázat (grid) kezeléséhez szükséges controllert is, amit az imént említett abstract controller-ből fogunk kiterjeszteni. A controller osztályt az app/code/Aion/Test/Controller/Adminhtml/Test könyvtárban lévő Index.php-ban valósítjuk meg. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Controller\Adminhtml\Test;

class Index extends \Aion\Test\Controller\Adminhtml\Test
{
    /**
     * @var \Magento\Framework\View\Result\PageFactory
     */
    protected $resultPageFactory;

    /**
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Framework\Registry $coreRegistry
     * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
     */
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\Registry $coreRegistry,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory
    ) {
        $this->resultPageFactory = $resultPageFactory;
        parent::__construct($context, $coreRegistry);
    }

    /**
     * Index action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        /** @var \Magento\Backend\Model\View\Result\Page $resultPage */
        $resultPage = $this->resultPageFactory->create();
        $this->initPage($resultPage)->getConfig()->getTitle()->prepend(__('Items'));
        return $resultPage;
    }
}

Az Index controller felelős az admin táblázat (grid) megjelenítésért. A Magento 2.0-ban minden controller osztály (file) valójában egy action-nek felel meg. Vagyis jelen esetben a Magento 1.x-ből megismert IndexAction() függvényt immár az execute() függvény helyettesíti. Tehát minden controller action-höz külön controller file tartozik és egyetlen execute() függvény. Ez első látásra felvetheti a kérdést, hogy miért jó ez? Lényegében így a teljes modul kódja jobban átlátható, mint a korábban Magento 1.x-ben lévő controller-ek esetében, amelyek sok esetben a fejlesztés végére hosszú script-et eredményeztek. A controller-ben lévő $resultPage és $this->resultPageFactory helyettesíti Magento 1.x-ből megismert $this->loadLayout() és $this->renderLayout() hívásokat.   Ahhoz, hogy a Magento 2.0 felismerje a létrehozott adminhtml controller-ek útvonalát, ezt definiálnunk kell egy külön fájl-ban. Az útvonal definiálását az app/code/Aion/Test/etc/adminhtml/ könyvtárban lévő routes.xml-ban valósítjuk meg. A fájl tartalma:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="admin">
        <route id="test" frontName="test">
            <module name="Aion_Test" before="Magento_Backend" />
        </route>
    </router>
</config>

A fájlban két lényeges tag és hozzá tartozó paraméter látható. Az első a <router id=”admin”> ami jelzi, hogy ez egy backend útvonal. A második a <route id=”test” frontName=”test”> ahol a frontName határozza meg az elkészült adminhtml controllerek fő útvonalát (az admin url utáni első paramétert). Következő lépésben létre kell hozni a collection-t, ami a fenti admin táblázatot (grid) fogja kiszolgálni adatokkal. A collection-t az app/code/Aion/Model/ResourceModel/Test/Grid/ könyvtárban lévő Collection.php-ban valósítjuk meg. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Model\ResourceModel\Test\Grid;

use Magento\Framework\Api\Search\SearchResultInterface;
use Magento\Framework\Search\AggregationInterface;
use Aion\Test\Model\ResourceModel\Test\Collection as TestCollection;

/**
 * Collection for displaying grid of Aion Items
 */
class Collection extends TestCollection implements SearchResultInterface
{
    /**
     * @var AggregationInterface
     */
    protected $aggregations;

    /**
     * @param \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory
     * @param \Psr\Log\LoggerInterface $logger
     * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy
     * @param \Magento\Framework\Event\ManagerInterface $eventManager
     * @param \Magento\Store\Model\StoreManagerInterface $storeManager
     * @param string $mainTable
     * @param string $eventPrefix
     * @param string $eventObject
     * @param string $resourceModel
     * @param string $model
     * @param string|null $connection
     * @param \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource
     *
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
     */
    public function __construct(
        \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory,
        \Psr\Log\LoggerInterface $logger,
        \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy,
        \Magento\Framework\Event\ManagerInterface $eventManager,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        $mainTable,
        $eventPrefix,
        $eventObject,
        $resourceModel,
        $model = 'Magento\Framework\View\Element\UiComponent\DataProvider\Document',
        $connection = null,
        \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource = null
    ) {
        parent::__construct(
            $entityFactory,
            $logger,
            $fetchStrategy,
            $eventManager,
            $storeManager,
            $connection,
            $resource
        );
        $this->_eventPrefix = $eventPrefix;
        $this->_eventObject = $eventObject;
        $this->_init($model, $resourceModel);
        $this->setMainTable($mainTable);
    }

    /**
     * @return AggregationInterface
     */
    public function getAggregations()
    {
        return $this->aggregations;
    }

    /**
     * @param AggregationInterface $aggregations
     * @return $this
     */
    public function setAggregations($aggregations)
    {
        $this->aggregations = $aggregations;
    }


    /**
     * Retrieve all ids for collection
     * Backward compatibility with EAV collection
     *
     * @param int $limit
     * @param int $offset
     * @return array
     */
    public function getAllIds($limit = null, $offset = null)
    {
        return $this->getConnection()->fetchCol($this->_getAllIdsSelect($limit, $offset), $this->_bindParams);
    }

    /**
     * Get search criteria.
     *
     * @return \Magento\Framework\Api\SearchCriteriaInterface|null
     */
    public function getSearchCriteria()
    {
        return null;
    }

    /**
     * Set search criteria.
     *
     * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria
     * @return $this
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function setSearchCriteria(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria = null)
    {
        return $this;
    }

    /**
     * Get total count.
     *
     * @return int
     */
    public function getTotalCount()
    {
        return $this->getSize();
    }

    /**
     * Set total count.
     *
     * @param int $totalCount
     * @return $this
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function setTotalCount($totalCount)
    {
        return $this;
    }

    /**
     * Set items list.
     *
     * @param \Magento\Framework\Api\ExtensibleDataInterface[] $items
     * @return $this
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function setItems(array $items = null)
    {
        return $this;
    }
}

A fájlban definiált osztály felelős a kialakítandó admin táblázatban(grid) az adatok hozzáadásáért, keresés és lapozás implementálásáért. A következőkben leírt UI Components megfelelő működéséhez szükséges a fenti függvények implementálása. Másik előnye, hogy az itt definiált osztályt könnyen máshol is fel tudjuk használni, amennyiben máshol is megszeretnénk jeleníteni a modul adatait admin táblázatban(grid), például egy product vagy customer ajax tab-on az adminisztrációs felületen.   Már csak egy dolog van hátra, ami nem más, mint az Index controllerhez tartozó layout file elkészítése. A layout file-t az app/code/Aion/Test/view/adminhtml/layout/ könyvtárban lévő test_test_index.xml-ban valósítjuk meg. Jól látható a korábban definiált route a fájl nevében: alap route -> könyvtár -> controller action. A fájl tartalma:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="content">
            <uiComponent name="test_test_listing"/>
        </referenceContainer>
    </body>
</page>

A layout fájlban lévő „content” referencia konténerben van definiálva a korábban említett UI component file neve, amire a következő pontban térünk ki részletesen.

2) UI Components avagy az admin táblázat (grid) új kialakítása

A Magento 2.0-ban bevezetett UI component-ek használatával sokkal könnyebben tudunk admin táblázatot (grid) létrehozni és emellett az adminisztrátor számára sokkal több lehetőséget nyújt a táblázatban történő kereséshez, szűréshez, az oszlopok tetszőleges megjelenítéséhez. Emellett külön megjelenítések menthetők le.   A UI component-ek működéséhez több fájlt is létre kell hoznunk és megfelelően implementálnunk. A legfontosabb fájl, ami az admin táblázat működését és megjelenését meghatározza egy xml fájl. Ez a mi modulunk esetében a test_test_listing.xml nevet kapta (lásd előző pont) és az app/code/Aion/Test/view/adminhtml/ui_component könyvtárban van elhelyezve. A fájl nagyon hosszú, így tartalmát darabolva jelenítjük meg cikkünkben.

<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">test_test_listing.test_test_listing_data_source</item>
            <item name="deps" xsi:type="string">test_test_listing.test_test_listing_data_source</item>
        </item>
        <item name="spinner" xsi:type="string">test_test_columns</item>
        <item name="buttons" xsi:type="array">
            <item name="add" xsi:type="array">
                <item name="name" xsi:type="string">add</item>
                <item name="label" xsi:type="string" translate="true">Add New Item</item>
                <item name="class" xsi:type="string">primary</item>
                <item name="url" xsi:type="string">*/*/new</item>
            </item>
        </item>
    </argument>
    <dataSource name="test_test_listing_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">TestGridDataProvider</argument>
            <argument name="name" xsi:type="string">test_test_listing_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">test_id</argument>
            <argument name="requestFieldName" xsi:type="string">id</argument>
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="update_url" xsi:type="url" path="mui/index/render"/>
                </item>
            </argument>
        </argument>
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>
            </item>
        </argument>
    </dataSource>
…

A fájlban lévő első argument tag-ben vannak definiálva az alábbiak:

  • adat forrás név (test_test_listing_data_source) lásd második dataSource tag: <dataSource name=”test_test_listing_data_source”>
  • oszlopok tag neve: test_test_columns, erre továbbiakban lesz szükségünk
  • az új elem hozzáadása és egyéb gombok definiálása, lásd: <item name=”buttons” xsi:type=”array”> tag
…
<container name="listing_top">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="template" xsi:type="string">ui/grid/toolbar</item>
        </item>
    </argument>
    <bookmark name="bookmarks">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="storageConfig" xsi:type="array">
                    <item name="namespace" xsi:type="string">test_test_listing</item>
                </item>
            </item>
        </argument>
    </bookmark>
    <component name="columns_controls">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="columnsData" xsi:type="array">
                    <item name="provider" xsi:type="string">test_test_listing.test_test_listing.test_test_columns</item>
                </item>
                <item name="component" xsi:type="string">Magento_Ui/js/grid/controls/columns</item>
                <item name="displayArea" xsi:type="string">dataGridActions</item>
            </item>
        </argument>
    </component>
…

Az xml fájlt tovább böngészve definiálásra kerülnek a táblázat felett elhelyezkedő funkciók. Ezek az alábbiak:

  • lementhetjük az aktuális táblázat megjelenést több view-ban, lásd: <bookmark name=”bookmarks”> tag
  • beállíthatjuk túl sok oszlop esetén, hogy melyek jelenjenek meg és az előbb említett bookmarks-nál menthetjük le, lásd: <component name=”columns_controls”> tag
…
<filterSearch name="fulltext">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="provider" xsi:type="string">test_test_listing.test_test_listing_data_source</item>
            <item name="chipsProvider" xsi:type="string">test_test_listing.test_test_listing.listing_top.listing_filters_chips</item>
            <item name="storageConfig" xsi:type="array">
                <item name="provider" xsi:type="string">test_test_listing.test_test_listing.listing_top.bookmarks</item>
                <item name="namespace" xsi:type="string">current.search</item>
            </item>
        </item>
    </argument>
</filterSearch>
<filters name="listing_filters">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="columnsProvider" xsi:type="string">test_test_listing.test_test_listing.test_test_columns</item>
            <item name="storageConfig" xsi:type="array">
                <item name="provider" xsi:type="string">test_test_listing.test_test_listing.listing_top.bookmarks</item>
                <item name="namespace" xsi:type="string">current.filters</item>
            </item>
            <item name="templates" xsi:type="array">
                <item name="filters" xsi:type="array">
                    <item name="select" xsi:type="array">
                        <item name="component" xsi:type="string">Magento_Ui/js/form/element/ui-select</item>
                        <item name="template" xsi:type="string">ui/grid/filters/elements/ui-select</item>
                    </item>
                </item>
            </item>
            <item name="childDefaults" xsi:type="array">
                <item name="provider" xsi:type="string">test_test_listing.test_test_listing.listing_top.listing_filters</item>
                <item name="imports" xsi:type="array">
                    <item name="visible" xsi:type="string">test_test_listing.test_test_listing.test_test_columns.${ $.index }:visible</item>
                </item>
            </item>
        </item>
    </argument>
</filters>
…

Hozzáadásra kerül a text alapú kereső és a táblázat szűrése (filters). Ezek az alábbiak:

  • varchar, text típusú oszlopokban kereshetünk egy input mezőben, lásd: <filterSearch name=”fulltext”> tag
  • minden egyes oszlopot szűrhetünk megfelelő paraméterek szerint view(Aion\Test\Ui\Component\Listing\Column\Test\Options), select, dátum, ID(range), text típusú szűrések, lásd: <filters name=”listing_filters”> tag
…
<massaction name="listing_massaction">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="selectProvider" xsi:type="string">test_test_listing.test_test_listing.test_test_columns.ids</item>
            <item name="indexField" xsi:type="string">test_id</item>
        </item>
    </argument>
    <action name="delete">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="type" xsi:type="string">delete</item>
                <item name="label" xsi:type="string" translate="true">Delete</item>
                <item name="url" xsi:type="url" path="test/test/massDelete"/>
                <item name="confirm" xsi:type="array">
                    <item name="title" xsi:type="string" translate="true">Delete items</item>
                    <item name="message" xsi:type="string" translate="true">Are you sure you wan't to delete selected items?</item>
                </item>
            </item>
        </argument>
    </action>
    <action name="disable">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="type" xsi:type="string">disable</item>
                <item name="label" xsi:type="string" translate="true">Disable</item>
                <item name="url" xsi:type="url" path="test/test/massDisable"/>
            </item>
        </argument>
    </action>
    <action name="enable">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="type" xsi:type="string">enable</item>
                <item name="label" xsi:type="string" translate="true">Enable</item>
                <item name="url" xsi:type="url" path="test/test/massEnable"/>
            </item>
        </argument>
    </action>
</massaction>
…

A massaction tag-en belül kerülnek hozzáadásra tetszőleges általunk definiált műveletek, melyek a tömeges adatmódosításra szolgálnak. A modulunkban ezek az alábbiak:

  • tömeges törlés, lásd: <action name=”delete”> tag
  • tömeges engedélyezés és tiltás, lásd: <action name=”disable”> és <action name=”enable”> tag-ek. Ezek a modulunk adatbázis táblájában korában kialakított is_active adatot módosítják.
…    
    <paging name="listing_paging">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="storageConfig" xsi:type="array">
                    <item name="provider" xsi:type="string">test_test_listing.test_test_listing.listing_top.bookmarks</item>
                    <item name="namespace" xsi:type="string">current.paging</item>
                </item>
                <item name="selectProvider" xsi:type="string">test_test_listing.test_test_listing.test_test_columns.ids</item>
            </item>
        </argument>
    </paging>
</container>
…

A <paging name=”listing_paging”> tag implementálja lapozást és a listázott elemek számának választhatóságát (select) a táblázatunkban.

…
<columns name="test_test_columns">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="storageConfig" xsi:type="array">
                <item name="provider" xsi:type="string">test_test_listing.test_test_listing.listing_top.bookmarks</item>
                <item name="namespace" xsi:type="string">current</item>
            </item>
        </item>
            <item name="childDefaults" xsi:type="array">
                <item name="fieldAction" xsi:type="array">
                    <item name="provider" xsi:type="string">test_test_listing.test_test_listing.test_test_columns_editor</item>
                    <item name="target" xsi:type="string">startEdit</item>
                    <item name="params" xsi:type="array">
                        <item name="0" xsi:type="string">${ $.$data.rowIndex }</item>
                        <item name="1" xsi:type="boolean">true</item>
                    </item>
                </item>
                <item name="storageConfig" xsi:type="array">
                    <item name="provider" xsi:type="string">test_test_listing.test_test_listing.listing_top.bookmarks</item>
                    <item name="root" xsi:type="string">columns.${ $.index }</item>
                    <item name="namespace" xsi:type="string">current.${ $.storageConfig.root }</item>
                </item>
            </item>
        </item>
    </argument>
    <selectionsColumn name="ids">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="indexField" xsi:type="string">test_id</item>
            </item>
        </argument>
    </selectionsColumn>
…

Fentiek után következik a táblázat oszlopainak meghatározása, lásd: <columns name=”test_test_columns”> tag. Ennek nevét már a fájl elején definiáltuk. A korábban említett mass action-ökhez beállított ID mező, lásd: <selectionsColumn name=”ids”> tag.

…   
        <column name="test_id">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">textRange</item>
                    <item name="sorting" xsi:type="string">asc</item>
                    <item name="label" xsi:type="string" translate="true">ID</item>
                </item>
            </argument>
        </column>
        <column name="name">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="editor" xsi:type="array">
                        <item name="editorType" xsi:type="string">text</item>
                        <item name="validation" xsi:type="array">
                            <item name="required-entry" xsi:type="boolean">true</item>
                        </item>
                    </item>
                    <item name="filter" xsi:type="string">text</item>
                    <item name="label" xsi:type="string" translate="true">Name</item>
                </item>
            </argument>
        </column>
        <column name="email">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="editor" xsi:type="array">
                        <item name="editorType" xsi:type="string">text</item>
                        <item name="validation" xsi:type="array">
                            <item name="required-entry" xsi:type="boolean">true</item>
                        </item>
                    </item>
                    <item name="filter" xsi:type="string">text</item>
                    <item name="label" xsi:type="string" translate="true">Email</item>
                </item>
            </argument>
        </column>
        <column name="creation_time" class="Magento\Ui\Component\Listing\Columns\Date">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">dateRange</item>
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/date</item>
                    <item name="dataType" xsi:type="string">date</item>
                    <item name="label" xsi:type="string" translate="true">Created</item>
                </item>
            </argument>
        </column>
        <column name="update_time" class="Magento\Ui\Component\Listing\Columns\Date">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">dateRange</item>
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/date</item>
                    <item name="dataType" xsi:type="string">date</item>
                    <item name="label" xsi:type="string" translate="true">Modified</item>
                </item>
            </argument>
        </column>
        <column name="sort_order">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="editor" xsi:type="array">
                        <item name="editorType" xsi:type="string">text</item>
                        <item name="validation" xsi:type="array">
                            <item name="required-entry" xsi:type="boolean">true</item>
                        </item>
                    </item>
                    <item name="filter" xsi:type="string">text</item>
                    <item name="label" xsi:type="string" translate="true">Sort Order</item>
                </item>
            </argument>
        </column>
        <column name="is_active">
            <argument name="data" xsi:type="array">
                <item name="options" xsi:type="array">
                    <item name="disable" xsi:type="array">
                        <item name="value" xsi:type="string">0</item>
                        <item name="label" xsi:type="string" translate="true">Disabled</item>
                    </item>
                    <item name="enable" xsi:type="array">
                        <item name="value" xsi:type="string">1</item>
                        <item name="label" xsi:type="string" translate="true">Enabled</item>
                    </item>
                </item>
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">select</item>
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/select</item>
                    <item name="editor" xsi:type="string">select</item>
                    <item name="dataType" xsi:type="string">select</item>
                    <item name="label" xsi:type="string" translate="true">Status</item>
                </item>
            </argument>
        </column>
        <actionsColumn name="actions" class="Aion\Test\Ui\Component\Listing\Column\TestActions">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="indexField" xsi:type="string">test_id</item>
                </item>
            </argument>
        </actionsColumn>
    </columns>
</listing>

Ezt követően a táblázatban lévő oszlopokat kell meghatároznunk. Az egyes oszlopoknál beállíthatjuk a típust, pl.: text, select, textRange, dateRange stb. Az utolsó oszlop az alap action-öket tartalmazza, lásd: <actionsColumn name=”actions” class=”Aion\Test\Ui\Component\Listing\Column\TestActions”> tag   Ezzel el is készültünk a grid definíciós xml-lel (test_test_listing.xml). A továbbiakban megnézünk néhány osztályt, ami az utolsó oszlopban lévő action-ökért felel.

3) UI component osztályok

Az előző pontban kialakított grid definíciós xml-ben található action oszlop működéséhez szükségünk van egy osztályra, mely a megjelenítést és a működést segítik.   Az első az előző pontban látható <actionsColumn name=”actions” class=”Aion\Test\Ui\Component\Listing\Column\TestActions”> tag-ben látható a TestActions osztály. A fájl az app/code/Aion/Test/Ui/Component/Listing/Column könyvtárban van elhelyezve TestActions.php néven. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Ui\Component\Listing\Column;

use Magento\Framework\UrlInterface;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Ui\Component\Listing\Columns\Column;

/**
 * Class TestActions
 */
class TestActions extends Column
{
    /**
     * Url path
     */
    const URL_PATH_EDIT = 'test/test/edit';
    const URL_PATH_DELETE = 'test/test/delete';

    /**
     * @var UrlInterface
     */
    protected $urlBuilder;

    /**
     * Constructor
     *
     * @param ContextInterface $context
     * @param UiComponentFactory $uiComponentFactory
     * @param UrlInterface $urlBuilder
     * @param array $components
     * @param array $data
     */
    public function __construct(
        ContextInterface $context,
        UiComponentFactory $uiComponentFactory,
        UrlInterface $urlBuilder,
        array $components = [],
        array $data = []
    ) {
        $this->urlBuilder = $urlBuilder;
        parent::__construct($context, $uiComponentFactory, $components, $data);
    }

    /**
     * Prepare Data Source
     *
     * @param array $dataSource
     * @return array
     */
    public function prepareDataSource(array $dataSource)
    {
        if (isset($dataSource['data']['items'])) {
            foreach ($dataSource['data']['items'] as & $item) {
                if (isset($item['test_id'])) {
                    $item[$this->getData('name')] = [
                        'edit' => [
                            'href' => $this->urlBuilder->getUrl(
                                static::URL_PATH_EDIT,
                                [
                                    'test_id' => $item['test_id']
                                ]
                            ),
                            'label' => __('Edit')
                        ],
                        'delete' => [
                            'href' => $this->urlBuilder->getUrl(
                                static::URL_PATH_DELETE,
                                [
                                    'test_id' => $item['test_id']
                                ]
                            ),
                            'label' => __('Delete'),
                            'confirm' => [
                                'title' => __('Delete "${ $.$data.name }"'),
                                'message' => __('Are you sure you wan\'t to delete a "${ $.$data.name }" record?')
                            ]
                        ]
                    ];
                }
            }
        }

        return $dataSource;
    }
}

Az osztály előállítja a mass action megjelenítéséhez szükséges tömböt a megfelelő formátumban. A file elején lévő konstantsoknál fontos a pontos útvonal meghatározása, hogy a megfelelő adminhtml controller-re mutassanak.

4) Adminhtml controller-ek

A grid teljes működéséhez néhány controller-t még el kell készíteni. Nézzük sorban őket. A tömeges törléshez a massDelete controller-t használjuk. A fájl az app/code/Aion/Test/Controller/Adminhtml/Test/ könyvtárban van elhelyezve MassDelete.php néven. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Controller\Adminhtml\Test;

use Magento\Framework\Controller\ResultFactory;
use Magento\Backend\App\Action\Context;
use Magento\Ui\Component\MassAction\Filter;
use Aion\Test\Model\ResourceModel\Test\CollectionFactory;

/**
 * Class MassDelete
 */
class MassDelete extends \Magento\Backend\App\Action
{
    /**
     * @var Filter
     */
    protected $filter;

    /**
     * @var CollectionFactory
     */
    protected $collectionFactory;

    /**
     * @param Context $context
     * @param Filter $filter
     * @param CollectionFactory $collectionFactory
     */
    public function __construct(Context $context, Filter $filter, CollectionFactory $collectionFactory)
    {
        $this->filter = $filter;
        $this->collectionFactory = $collectionFactory;
        parent::__construct($context);
    }

    /**
     * Execute action
     *
     * @return \Magento\Backend\Model\View\Result\Redirect
     * @throws \Magento\Framework\Exception\LocalizedException|\Exception
     */
    public function execute()
    {
        $collection = $this->filter->getCollection($this->collectionFactory->create());
        $collectionSize = $collection->getSize();

        foreach ($collection as $item) {
            $item->delete();
        }

        $this->messageManager->addSuccess(__('A total of %1 record(s) have been deleted.', $collectionSize));

        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
        return $resultRedirect->setPath('*/*/');
    }
}

A controller osztály execute() függvénye – vagyis az action – egy collection-t kap (\Magento\Ui\Component\MassAction\Filter osztálytól), amin végig iterálva törli az elemeket. A tömeges státusz módosításhoz a massEnable és massDisable controller-eket használjuk. A fájlok az app/code/Aion/Test/Controller/Adminhtml/Test/ könyvtárban vannak elhelyezve MassEnable.php és MassDisable.php néven. A fájlok tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Controller\Adminhtml\Test;

use Magento\Framework\Controller\ResultFactory;
use Magento\Backend\App\Action\Context;
use Magento\Ui\Component\MassAction\Filter;
use Aion\Test\Model\ResourceModel\Test\CollectionFactory;

/**
 * Class MassEnable
 */
class MassEnable extends \Magento\Backend\App\Action
{
    /**
     * @var Filter
     */
    protected $filter;

    /**
     * @var CollectionFactory
     */
    protected $collectionFactory;

    /**
     * @param Context $context
     * @param Filter $filter
     * @param CollectionFactory $collectionFactory
     */
    public function __construct(Context $context, Filter $filter, CollectionFactory $collectionFactory)
    {
        $this->filter = $filter;
        $this->collectionFactory = $collectionFactory;
        parent::__construct($context);
    }

    /**
     * Execute action
     *
     * @return \Magento\Backend\Model\View\Result\Redirect
     * @throws \Magento\Framework\Exception\LocalizedException|\Exception
     */
    public function execute()
    {
        $collection = $this->filter->getCollection($this->collectionFactory->create());

        foreach ($collection as $item) {
            $item->setIsActive(true);
            $item->save();
        }

        $this->messageManager->addSuccess(__('A total of %1 record(s) have been enabled.', $collection->getSize()));

        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
        return $resultRedirect->setPath('*/*/');
    }
}
<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Controller\Adminhtml\Test;

use Magento\Framework\Controller\ResultFactory;
use Magento\Backend\App\Action\Context;
use Magento\Ui\Component\MassAction\Filter;
use Aion\Test\Model\ResourceModel\Test\CollectionFactory;

/**
 * Class MassDisable
 */
class MassDisable extends \Magento\Backend\App\Action
{
    /**
     * @var Filter
     */
    protected $filter;

    /**
     * @var CollectionFactory
     */
    protected $collectionFactory;

    /**
     * @param Context $context
     * @param Filter $filter
     * @param CollectionFactory $collectionFactory
     */
    public function __construct(Context $context, Filter $filter, CollectionFactory $collectionFactory)
    {
        $this->filter = $filter;
        $this->collectionFactory = $collectionFactory;
        parent::__construct($context);
    }

    /**
     * Execute action
     *
     * @return \Magento\Backend\Model\View\Result\Redirect
     * @throws \Magento\Framework\Exception\LocalizedException|\Exception
     */
    public function execute()
    {
        $collection = $this->filter->getCollection($this->collectionFactory->create());

        foreach ($collection as $item) {
            $item->setIsActive(false);
            $item->save();
        }

        $this->messageManager->addSuccess(__('A total of %1 record(s) have been disabled.', $collection->getSize()));

        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
        return $resultRedirect->setPath('*/*/');
    }
}

A két controller működése nagyon hasonló. Mindkettő a Filter osztálytól kapott collection-ön iterál végig, és állítja be az is_active data kulcsot massEnbale esetén true-ra, míg massDisable esetén false-ra, majd menti a collection elemeit.

5) Object manager konfigurációs

Ahhoz, hogy az elkészített admin táblázat(grid) megfelelően működjön, meg kell adnunk a forrás adat objektumokat és filter-eket. Ehhez szükségünk lesz egy definíciós xml-re. A fájl az app/code/Aion/Test/etc/ könyvtárban van elhelyezve di.xml néven. A fájl tartalma:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">
        <arguments>
            <argument name="collections" xsi:type="array">
                <item name="test_test_listing_data_source" xsi:type="string">Aion\Test\Model\ResourceModel\Test\Grid\Collection</item>
            </argument>
        </arguments>
    </type>
    <type name="Aion\Test\Model\ResourceModel\Test\Grid\Collection">
        <arguments>
            <argument name="mainTable" xsi:type="string">aion_test</argument>
            <argument name="eventPrefix" xsi:type="string">aion_test_grid_collection</argument>
            <argument name="eventObject" xsi:type="string">test_grid_collection</argument>
            <argument name="resourceModel" xsi:type="string">Aion\Test\Model\ResourceModel\Test</argument>
        </arguments>
    </type>
    <virtualType name="TestGirdFilterPool" type="Magento\Framework\View\Element\UiComponent\DataProvider\FilterPool">
        <arguments>
            <argument name="appliers" xsi:type="array">
                <item name="regular" xsi:type="object">Magento\Framework\View\Element\UiComponent\DataProvider\RegularFilter</item>
                <item name="fulltext" xsi:type="object">Magento\Framework\View\Element\UiComponent\DataProvider\FulltextFilter</item>
            </argument>
        </arguments>
    </virtualType>
    <virtualType name="TestGridDataProvider" type="Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider">
        <arguments>
            <argument name="collection" xsi:type="object" shared="false">Aion\Test\Model\ResourceModel\Test\Collection</argument>
            <argument name="filterPool" xsi:type="object" shared="false">TestGirdFilterPool</argument>
        </arguments>
    </virtualType>
</config>

Ebben a fájlban definiáljuk a grid-hez szükséges collection-t (lásd: <item name=”test_test_listing_data_source” xsi:type=”string”>Aion\Test\Model\ResourceModel\Test\Grid\Collection</item>), filter-t és data provider-t, mely UI component megfelelő működéséhez szükséges.   Az egyes elemek editálását, mentését és egyenkénti törlését a következőkben írjuk le.

6) Editáláshoz szükséges admin block-ok létrehozása

Ahhoz, hogy a modulhoz tartozó adatokat létre tudjuk hozni az admin felületen és szerkeszteni tudjuk, szükségünk lesz a megfelelő osztályokra. Első lépésben a container osztályt kell létrehozni, mely később a form-ot fogja tartalmazni. Az osztályt az Aion/Test/Block/Adminhtml/ könyvtárban lévő Test.php fájlban valósítjuk meg:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Block\Adminhtml;

/**
 * Adminhtml Aion items content block
 */
class Test extends \Magento\Backend\Block\Widget\Grid\Container
{
    /**
     * @return void
     */
    protected function _construct()
    {
        $this->_blockGroup = 'Aion_Test';
        $this->_controller = 'adminhtml_test';
        $this->_headerText = __('Items');
        $this->_addButtonLabel = __('Add New Item');
        parent::_construct();
    }
}

Az osztályban lényeges a megfelelő blockGroup és controller meghatározása.   A következő lépésben szükségünk lesz a form container osztályra. Itt határozzuk meg szerkesztetés alatt álló objektum admin oldalának title-jét, és adhatunk hozzá tetszőleges button-okat az alap gombokon kívül, vagy távolíthatunk el. Az osztályt az Aion/Test/Block/Adminhtml/Test könyvtárban lévő Edit.php fájlban valósítjuk meg:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Block\Adminhtml\Test;

/**
 * Aion item edit form container
 */
class Edit extends \Magento\Backend\Block\Widget\Form\Container
{
    /**
     * Core registry
     *
     * @var \Magento\Framework\Registry
     */
    protected $_coreRegistry = null;

    /**
     * @param \Magento\Backend\Block\Widget\Context $context
     * @param \Magento\Framework\Registry $registry
     * @param array $data
     */
    public function __construct(
        \Magento\Backend\Block\Widget\Context $context,
        \Magento\Framework\Registry $registry,
        array $data = []
    ) {
        $this->_coreRegistry = $registry;
        parent::__construct($context, $data);
    }

    /**
     * @return void
     */
    protected function _construct()
    {
        $this->_objectId = 'test_id';
        $this->_blockGroup = 'Aion_Test';
        $this->_controller = 'adminhtml_test';

        parent::_construct();

        $this->buttonList->update('save', 'label', __('Save Item'));
        $this->buttonList->update('delete', 'label', __('Delete Item'));

        $this->buttonList->add(
            'saveandcontinue',
            [
                'label' => __('Save and Continue Edit'),
                'class' => 'save',
                'data_attribute' => [
                    'mage-init' => ['button' => ['event' => 'saveAndContinueEdit', 'target' => '#edit_form']],
                ]
            ],
            -100
        );

    }

    /**
     * Get edit form container header text
     *
     * @return \Magento\Framework\Phrase
     */
    public function getHeaderText()
    {
        if ($this->_coreRegistry->registry('test_item')->getId()) {
            return __("Edit Block '%1'", $this->escapeHtml($this->_coreRegistry->registry('test_item')->getName()));
        } else {
            return __('New Item');
        }
    }
}

Amennyiben WYSWYG editort is szeretnénk használni például textarea típusú mezőhöz, akkor azt a _construct() függvényben kell elhelyezni, vagy a prepareLayout() függvényben. Az osztályban lévő getHeaderText() függvény határozza meg az admin oldal title értékét.   Az utolsó block, amit el kell készítenünk, a form megjelenítését és kezelését végzi. Az osztályt az Aion/Test/Block/Adminhtml/Test/Edit könyvtárban lévő Form.php fájlban valósítjuk meg:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Block\Adminhtml\Test\Edit;

/**
 * Adminhtml Aion item edit form
 */
class Form extends \Magento\Backend\Block\Widget\Form\Generic
{
    /**
     * @var \Magento\Cms\Model\Wysiwyg\Config
     */
    protected $_wysiwygConfig;

    /**
     * @var \Magento\Store\Model\System\Store
     */
    protected $_systemStore;

    /**
     * @param \Magento\Backend\Block\Template\Context $context
     * @param \Magento\Framework\Registry $registry
     * @param \Magento\Framework\Data\FormFactory $formFactory
     * @param \Magento\Cms\Model\Wysiwyg\Config $wysiwygConfig
     * @param \Magento\Store\Model\System\Store $systemStore
     * @param array $data
     */
    public function __construct(
        \Magento\Backend\Block\Template\Context $context,
        \Magento\Framework\Registry $registry,
        \Magento\Framework\Data\FormFactory $formFactory,
        \Magento\Cms\Model\Wysiwyg\Config $wysiwygConfig,
        \Magento\Store\Model\System\Store $systemStore,
        array $data = []
    ) {
        $this->_wysiwygConfig = $wysiwygConfig;
        $this->_systemStore = $systemStore;
        parent::__construct($context, $registry, $formFactory, $data);
    }

    /**
     * Init form
     *
     * @return void
     */
    protected function _construct()
    {
        parent::_construct();
        $this->setId('test_form');
        $this->setTitle(__('Item Information'));
    }

    /**
     * Prepare form
     *
     * @return $this
     */
    protected function _prepareForm()
    {
        $model = $this->_coreRegistry->registry('test_item');

        /** @var \Magento\Framework\Data\Form $form */
        $form = $this->_formFactory->create(
            ['data' => ['id' => 'edit_form', 'action' => $this->getData('action'), 'method' => 'post']]
        );

        $form->setHtmlIdPrefix('item_');

        $fieldset = $form->addFieldset(
            'base_fieldset',
            ['legend' => __('General Information'), 'class' => 'fieldset-wide']
        );

        if ($model->getId()) {
            $fieldset->addField('test_id', 'hidden', ['name' => 'test_id']);
        }

        $fieldset->addField(
            'name',
            'text',
            [
                'name' => 'name',
                'label' => __('Name'),
                'title' => __('Name'),
                'required' => true
            ]
        );

        $fieldset->addField(
            'email',
            'text',
            [
                'name' => 'email',
                'label' => __('Email'),
                'title' => __('Email'),
                'required' => true,
                'class' => 'validate-email'
            ]
        );

        $fieldset->addField(
            'is_active',
            'select',
            [
                'label' => __('Status'),
                'title' => __('Status'),
                'name' => 'is_active',
                'required' => true,
                'options' => ['1' => __('Enabled'), '0' => __('Disabled')]
            ]
        );
        if (!$model->getId()) {
            $model->setData('is_active', '1');
        }

        $fieldset->addField(
            'sort_order',
            'text',
            [
                'name' => 'sort_order',
                'label' => __('Sort Order'),
                'title' => __('Sort Order'),
                'required' => false
            ]
        );

        $form->setValues($model->getData());
        $form->setUseContainer(true);
        $this->setForm($form);

        return parent::_prepareForm();
    }
}

Az osztály _prepareForm() függvényében adjuk hozzá a szerkesztésre szánt mezőket, ami a mi esetünkben a name, email és sort_order mezők. Ezek mellett szerepel még a multistore kezelés szempontjából fontos store_id field is, illetve is_active field is, ami jelen esetben select típusú és a szerkesztés alatt álló elem státuszát hivatott beállítani.   A fent említett három osztállyal el is készítettük az adminisztrációs felületen történő szerkesztéshez szükséges fájlokat.

7) Controller-ek és layout létrehozása

Az editálás megvalósításához a fenti osztályokon kívül szükségünk lesz még a megfelelő controller osztályokra és layout fájlokra.   Az első osztályt az Aion/Test/Controller/Adminhtml/Test/ könyvtárban lévő NewAction.php fájlban valósítjuk meg:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Controller\Adminhtml\Test;

class NewAction extends \Aion\Test\Controller\Adminhtml\Test
{
    /**
     * @var \Magento\Backend\Model\View\Result\ForwardFactory
     */
    protected $resultForwardFactory;

    /**
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Framework\Registry $coreRegistry
     * @param \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory
     */
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\Registry $coreRegistry,
        \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory
    ) {
        $this->resultForwardFactory = $resultForwardFactory;
        parent::__construct($context, $coreRegistry);
    }

    /**
     * Create new item
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        /** @var \Magento\Framework\Controller\Result\Forward $resultForward */
        $resultForward = $this->resultForwardFactory->create();
        return $resultForward->forward('edit');
    }
}

Az osztály az új elemek létrehozására szolgál és lényegében az action függvénye (execute()) átirányít az Edit controller osztályra.   A következő lépésben létrehozzuk a szerkesztéshez szükséges controller-t. Az osztályt az Aion/Test/Controller/Adminhtml/Test/ könyvtárban lévő Edit.php fájlban valósítjuk meg:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Controller\Adminhtml\Test;

class Edit extends \Aion\Test\Controller\Adminhtml\Test
{
    /**
     * @var \Magento\Framework\View\Result\PageFactory
     */
    protected $resultPageFactory;

    /**
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Framework\Registry $coreRegistry
     * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
     */
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\Registry $coreRegistry,
        \Magento\Framework\View\Result\PageFactory $resultPageFactory
    ) {
        $this->resultPageFactory = $resultPageFactory;
        parent::__construct($context, $coreRegistry);
    }

    /**
     * Edit item
     *
     * @return \Magento\Framework\Controller\ResultInterface
     * @SuppressWarnings(PHPMD.NPathComplexity)
     */
    public function execute()
    {
        // 1. Get ID and create model
        $id = $this->getRequest()->getParam('test_id');
        $model = $this->_objectManager->create('Aion\Test\Model\Test');

        // 2. Initial checking
        if ($id) {
            $model->load($id);
            if (!$model->getId()) {
                $this->messageManager->addError(__('This item no longer exists.'));
                /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
                $resultRedirect = $this->resultRedirectFactory->create();
                return $resultRedirect->setPath('*/*/');
            }
        }
        // 3. Set entered data if was error when we do save
        $data = $this->_objectManager->get('Magento\Backend\Model\Session')->getFormData(true);
        if (!empty($data)) {
            $model->setData($data);
        }

        // 4. Register model to use later in blocks
        $this->_coreRegistry->register('test_item', $model);

        /** @var \Magento\Backend\Model\View\Result\Page $resultPage */
        $resultPage = $this->resultPageFactory->create();

        // 5. Build edit form
        $this->initPage($resultPage)->addBreadcrumb(
            $id ? __('Edit Item') : __('New Item'),
            $id ? __('Edit Item') : __('New Item')
        );
        $resultPage->getConfig()->getTitle()->prepend(__('Items'));
        $resultPage->getConfig()->getTitle()->prepend($model->getId() ? $model->getName() : __('New Item'));
        return $resultPage;
    }
}

Az edit action(execute() függvény) első lépésben lekéri a test_id paramétert. Ezt követően inicializálja az Aion/Test/Model/Test modell osztályt. Amennyiben a test_id paraméternek van értéke, a modellt megpróbálja betöltelni az említett id-val. Sikertelen esetben hibaüzenet állít be, majd visszairányít. Ellenkező esetben a betöltött modellt a registry-ben tárolja ($this->_coreRegistry->register(’test_item’, $model)). Ezt olvassa ki a registry-ből a fent már említett form container osztály is, és használja fel. Végezetül létrehozza az oldalt ($resultPage), majd beállítja az oldal title paraméterét és breadcrumb-ot is.   A controller-hez tartozó layout fájlt az Aion/Test/view/adminhtml/layout/ könyvtárban lévő test_test_edit.xml fájlban valósítjuk meg:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <update handle="editor"/>
    <body>
        <referenceContainer name="content">
            <block class="Aion\Test\Block\Adminhtml\Test\Edit" name="test_test_edit"/>
        </referenceContainer>
    </body>
</page>

A következő lépés a mentés elkészítése. Az osztályt az Aion/Test/Controller/Adminhtml/Test/ könyvtárban lévő Save.php fájlban valósítjuk meg:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Controller\Adminhtml\Test;

class Save extends \Aion\Test\Controller\Adminhtml\Test
{
    /**
     * Save action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultRedirectFactory->create();
        // check if data sent
        $data = $this->getRequest()->getPostValue();
        if ($data) {
            $id = $this->getRequest()->getParam('test_id');
            $model = $this->_objectManager->create('Aion\Test\Model\Test')->load($id);
            if (!$model->getId() && $id) {
                $this->messageManager->addError(__('This item no longer exists.'));
                return $resultRedirect->setPath('*/*/');
            }

            // init model and set data

            $model->setData($data);

            // try to save it
            try {
                // save the data
                $model->save();
                // display success message
                $this->messageManager->addSuccess(__('You saved the item.'));
                // clear previously saved data from session
                $this->_objectManager->get('Magento\Backend\Model\Session')->setFormData(false);

                // check if 'Save and Continue'
                if ($this->getRequest()->getParam('back')) {
                    return $resultRedirect->setPath('*/*/edit', ['test_id' => $model->getId()]);
                }
                // go to grid
                return $resultRedirect->setPath('*/*/');
            } catch (\Exception $e) {
                // display error message
                $this->messageManager->addError($e->getMessage());
                // save data in session
                $this->_objectManager->get('Magento\Backend\Model\Session')->setFormData($data);
                // redirect to edit form
                return $resultRedirect->setPath('*/*/edit', ['test_id' => $this->getRequest()->getParam('test_id')]);
            }
        }
        return $resultRedirect->setPath('*/*/');
    }
}

A controller osztály első lépésben várja a korábban kialakított form által posztolt adatokat ($data = $this->getRequest()->getPostValue();). Amennyiben ez nem egy üres tömb, inicializálja az Aion/Test/Model/Test modell osztályt és ha létezik a paraméterként kapott test_id is (vagyis nem új objektum mentésére kerül sor), akkor betölti a megfelelő id-val. Ezt követően a post-ban kapott adatokat beállítja majd menti a modellt. Amennyiben mindezzel elkészültünk, akkor a korábban kialakított admin grid-ből (táblázat) új objektumokat tudunk hozzáadni és menteni, majd ezeket szerkeszteni. Még egy fontos controller van hátra, ami a törlést hivatott megvalósítani. Az osztályt az Aion/Test/Controller/Adminhtml/Test/ könyvtárban lévő Delete.php fájlban valósítjuk meg:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Controller\Adminhtml\Test;

class Delete extends \Aion\Test\Controller\Adminhtml\Test
{
    /**
     * Delete action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultRedirectFactory->create();
        // check if we know what should be deleted
        $id = $this->getRequest()->getParam('test_id');
        if ($id) {
            try {
                // init model and delete
                $model = $this->_objectManager->create('Aion\Test\Model\Test');
                $model->load($id);
                $model->delete();
                // display success message
                $this->messageManager->addSuccess(__('You deleted the item.'));
                // go to grid
                return $resultRedirect->setPath('*/*/');
            } catch (\Exception $e) {
                // display error message
                $this->messageManager->addError($e->getMessage());
                // go back to edit form
                return $resultRedirect->setPath('*/*/edit', ['test_id' => $id]);
            }
        }
        // display error message
        $this->messageManager->addError(__('We can\'t find the item to delete.'));
        // go to grid
        return $resultRedirect->setPath('*/*/');
    }
}

A delete action (execute() függvény) első lépésben lekéri a test_id paramétert. Ezt követően inicializálja az Aion/Test/Model/Test model osztályt. Amennyiben a test_id paraméternek van értéke, a modellt megpróbálja betölteni az említett id-val, majd elvégzi a törlést.   Bízom benne, hogy ebben a kétrészes átfogó cikkben sikerült átadnom mindazt a tudnivalót, mellyel sikeresen hozhatsz létre te is egy saját modult a Magento 2 rendszerben, és állíthatsz be, illetve szerkeszthetsz különböző elemeket, fájlokat hozzá, mint pl. adatbázis tábla, modell, collection, block, admin táblázat, layout stb.   A cikk első része itt olvasható: Magento 2 modul fejlesztés lépésről lépésre – 1. rész

 

Amit mindenképp tudnod kell az e-kereskedelmi blogolásról

Meggyőzünk a számokkal

Mégis, mennyire éri meg blogolni? Mit érhetünk el azzal, hogy szöveget helyezünk egy olyan oldalra, ahol semmi keresnivalója nem lenne – hiszen a cél az, hogy a beérkező látogatókat a termékhez irányítsuk, konverzióra bírjuk, és nem az, hogy olvasgassanak! Rendben, ha egy kicsit is otthonosan mozgunk az online marketingben, tudhatjuk, hogy az új látogatók vonzásának egyik legjobb eszköze éppen a tartalom. Ahol a direkt reklámok kudarcot mondanak, ott a content képes arra, hogy nagy mennyiségű új emberhez juttasson el minket.

Látogatók

Átlagosan a blogra érkező látogatók 80%-a először jár ott – ami azt jelenti, hogy egy nagyszerű eszközt kapsz arra, hogy megragadd azokat, akik még nem ismerik a webáruházadat. Az új vásárlók megnyerése mindig nehezebb (és költségesebb), mint a meglévők megtartása. Azzal, hogy értékes tartalmat szolgáltatsz nekik, máris megtetted az első lépést. Webáruház blog látogatók Nem elhanyagolandó az a tény sem, hogy egy bloggal rendelkező céges oldal 55%-kal több látogatót vonz. A felhasználók keresik az információt, az érdekes tartalom megragadja őket, a számukra releváns szövegeket szívesen olvassák.

Linkek

Hogyan hat egy blog a linképítésre? Elképesztően hatékonyan. A blogoló cégek átlagosan 97%-kal több bejövő (inbound) hivatkozást képesek generálni, mint nem blogoló versenytársaik. Még jobb, ha videókat is készítesz, és azokat megosztod a céges blogban, a videót tartalmazó posztok ugyanis 3-szor annyi hivatkozást generálnak, mint az anélküliek. Szintén hatékony a SEO-t (keresőoptimalizálást) tekintve, hogy minél több posztot teszünk közzé, annál könnyebben megtalálhatóvá válunk. Ha minőségi tartalmat teszel közzé, amelyet a felhasználók elolvasnak, megosztanak, akkor egyre több oldalad kerül majd előkelő helyezésekbe a keresők találati oldalain.

Leadek

De hogyan lesz mindebből pénz? Nos, a statisztikák szerint egy bogot vezető B2B vállalat 67 százalékkal több leadet generál havonta, mint egy blogot nem használó. Beérkező megkeresések Az online marketingesek több mint kétharmada az öt leghatékonyabb leadgeneráló eszköz közé sorolja a blogokat, 2015-ben pedig 68 százalékuk tervezte, hogy fokozza blogolási tevékenységét. Azt említettük már, hogy azok a marketingesek, akik előtérbe helyezik a blogolást, 13-szor nagyobb eséllyel élvezhetnek pozitív ROI-t?

Mit és hogyan írj?

Egy céges blog haszna az e-kereskedelemben felbecsülhetetlen: egy olyan saját csatornát nyersz vele, ahol a legfontosabb iparági híreket közölheted, brandedet építheted, ahol gyakori kérdéseket válaszolhatsz meg, ahol anélkül írhatsz releváns és hasznos szövegeket termékeidről (amelyek aztán sok vásárlót győzhetnek meg arról, hogy érdemes pénzt kiadniuk), hogy „nyomulva” akarnál eladni bármit is. Használhatod egyfajta ügyfélszolgálati platformként is: közzétehetsz itt pozitív véleményeket, testimonialokat, amelyek segítenek megbízhatóbb színben feltüntetni téged. Kezelheted nyilvánosan a kifogásokat, reklamációkat, a transzparenciával rokonszenvet érve el és egyben eloszlatva az új, potenciális vásárlók bizonyos kétségeit a céggel vagy a termékkel kapcsolatban. A blog segítségével nemcsak egyszerűen kommunikálhatsz a vásárlókkal. Ha megfelelően érdekesen írsz, közönséget építhetsz, amely már csak azért is visszajár majd, hogy írásaidat olvassa – attól függetlenül, hogy éppen akarnak-e tőled vásárolni.

Így találhatsz jó témákat…

Mielőtt elkezdenél blogolni, érdemes felmérned célközönségedet. Mindenekelőtt nézd meg, hogy milyen kérdésekre, kifejezésekre, kulcsszavakra keresnek rá a keresőmotorokat használva – így máris rengeteg témát találsz, a melyeket érdemes lehet feldolgozni. Blogbejegyzés ötletek összeírása Keresd meg a releváns csoportokat a közösségi médiában, és figyeld meg, miről beszélgetnek a felhasználók. Milyen témákban kérnek segítséget a fórumokon? Melyek azok a kérdések, amelyek viszonylag gyakran felmerülnek? Ezek megválaszolására alaposan kidolgozott, részletes bejegyzéseket készíthetsz, amelyek nemcsak SEO-szempontból erősítenek majd téged, hanem azzal, hogy releváns és értékes válaszokat adsz, szakértői státuszodat is megerősítik a célközönséged szemében. Remek témákat találhatsz akkor is, ha az olyan oldalakat tekinted át, amelyek kifejezetten a kérdés-válasz formátumra épülnek. Itthon a legismertebb ilyen talán a hírhedt Gyakorikérdések, külföldön a szakemberek az igen magas színvonalú Quorát célozzák meg legelőször. Optimális esetben soha nem fogysz ki a témából, de a folyamatot egyszerűbbé is teheted. Egyszerre soha ne csak egy témát keress, hanem egy-két tucatnyit, hogy jó előre felkészülhess. Így több időd marad arra is, hogy forrásokat gyűjts, utánaolvass, aminek kell, hogy kiforrottabb bejegyzéseket készíts. Használj publikációs naptárat: határozd meg, legalább két hétre előre, hogy mikor milyen témát dolgozol majd ki, mikor jelennek majd meg a bejegyzéseid a blogon.

Stílus, hangvétel

Hogy milyen stílusban akarsz kommunikálni, rajtad áll – emberközeli, barátságos brandet akarsz építeni, vagy inkább egy távolságtartóbb, keményen szakértői képet mutatnál? Ezt határozd meg és tartsd magad hozzá hosszú távon is. Ne változtass a stílusodon bejegyzésenként: a cél az, hogy egy konzekvens képet alakíts ki magadról, amit megszoknak és felismernek majd azok, akik rendszeresen olvasnak. Fontos az is, hogy a stílus egyezzen a más csatornákon mutatott képeddel. Igyekezz hasonlóan kommunikálni e-mailben, a közösségi médiában és a blogodon is.

Az írásról

Maga a szöveg nem biztos, hogy jó lesz. Sok cég inkább profi szövegírókhoz fordul, hogy céges blogjaikat vezessék, mivel a több ezer órányi tapasztalatot nem mindig pótolja a szenvedély. Miről és hogyan írj a blogodba? Ugyanakkor néhány egyszerű alapelv betartásával eredményes és élvezetes szövegeket készíthetsz te magad is. Némely esetben kifejezetten előny, ha nem szervezed ki az írást – ügyfélkapcsolati jellegű írásoknál, kifogáskezelésnél, a termékek nagyon alapos bemutatásánál nem árt, ha olyan írja a szöveget, akinek a tapasztalatai első kézből származnak. Figyelj arra, hogy olvasmányos szöveget szerkessz: ne használj nagyobb szövegtömböket, néhány soronként kezdj új bekezdést. Használj sok-sok alcímet, amelyek rövidek és felkeltik a figyelmet. Ezeknek a funkciója, hogy a szöveget csak átfutó olvasó könnyen megtalálja azt, ami őt érdekli. Ha például egy adott termékfajtáról írsz, valószínűleg őt egy-két konkrét kérdés érdekli majd, érdemes tehát a szöveget úgy megszerkesztened, hogy ezekre már egy gyors görgetéskor is felfigyeljen. A főbb üzeneteket, mondanivalókat mindig emeld ki, és az sem árt, ha illusztrációkat használsz. Sőt, akár a korábban elkészített videóidat is felhasználhatod: egyes felhasználói csoportok sokkal szívesebben néznek meg egy informatív pár perces videót, minthogy egy hosszabb szakmai jellegű írást elolvassanak (ilyenek például a cégvezetők). Ezért is fontos, hogy…

Mutáld a tartalmakat!

Ha már blogot írsz, akkor használd fel a tartalmaidat más felületeken, más formában is. Egy kiemelkedően jól sikerült szakcikkből infografika készülhet, több hasonló témájú anyagodat összefűzve letölthető e-könyvet szerkeszthetsz. Egy olyan témát, amit a közönség imád, többször, több formában is feldolgozhatsz, sőt, ezt kötelező megtenned. A legjobb blogok is ismétlik önmagukat, és nem véletlenül: felfoghatod ezt egyfajta A/B tesztnek is, ahol a különféle buyer personákat másfajta tartalommal célzod meg, vagy ugyanannál a közönségnél megpróbálhatsz nagyobb sikert elérni.

Terjessz, ahol csak tudsz

Figyelj arra is, hogy minden egyes cikkedet a lehető legtöbb felületen oszd meg. A Facebook oldal például ideális erre: legjobb anyagaidat bizonyos időközönként újra és újra publikálhatod. Kis kísérletezéssel megtudhatod, melyek a számodra legjobb csatornák – az is lehet, hogy neked a legjobb közösségi felületet nem a Facebook jelenti majd, hanem a LinkedIn vagy éppen az Instagram. Ekereskedelem blog tartalom Ne csak a saját oldalaidon publikálj: a különféle csoportokban, fórumokon is oszd meg a bejegyzéseket, ott, ahol a célközönséged organikus módon megtalálja azokat. Érdemes automatizált hírlevelet is küldened. Készíts egy csalit (például egy e-könyvet) amelyért cserébe csak egy e-mail címet kérsz a felhasználótól. Így havonta egy-két alkalommal elküldheted neki a legnépszerűbb vagy éppen számára legrelevánsabb posztokat – és persze termékeidet is ajánlhatod neki.

Nem kell magányosan dolgoznod

A blogod sokkal nagyobb közönséget fog vonzani akkor, ha valamiképpen hatni tudsz az online véleményvezérekre. Ezek lehetnek olyan szerzők, akik fogyasztói blogokat vezetnek, elismert szakértők a szakterületeden vagy éppen netcelebek, akik valamilyen szempontból a webáruházadra nézve releváns tevékenységet végeznek. A legjobb, ha írásaidat ők is megosztják, hivatkozzák. Értelemszerűen ez azt eredményezi, hogy sokkal láthatóbbá válsz, mintha csak saját csatornáidon osztanád a tartalmakat, ezzel úton vagy a viralitás felé. Kapcsolatba léphetsz velük azonban ennél közvetlenebb módon is. Felkérheted őket arra, hogy cikkeket írjanak a blogodra, te magad pedig vendégposztokat küldhetsz más olvasott blogokba. Mindez szintén ingyen forgalmat hoz neked úgy, hogy segít a linkprofilod építésében és ezzel SEO-ban erősít.

A sikerre várnod kell, de megéri

A blogolás olyan stratégia, amely nem egy-két hét alatt hoz neked eredményeket. Hosszú távú befektetés, amely viszont biztosan megtérül, ha odafigyelsz néhány apróságra. Az is lehet, hogy te magad csak egy év után jössz majd rá, hogy valójában miről is szól a blogod. Lehet, hogy úgy vágsz bele, hogy te egy adott témáról szeretnél írni, idővel azonban kiderülhet, hogy a közönséged valami egészen másra vevő. Még akár arra is rájöhetsz, hogy más a közönséged, mint eredetileg gondoltad. A blognak meg kell érnie, elegendő tapasztalatot és főleg adatot kell begyűjtened ahhoz, hogy bármi biztosat állíts róla. Egy év elteltével pontosan tudni fogod, hogy mely témákat, mely tartalomformátumokat kedvelik a célközönségedbe tartozók. Hogy mely posztok generálnak leadeket és melyek csak egyszerűen látogatókat. Hogy milyen stílusban kell szólnod. Ekkorra megerősödsz, hiszen visszatérő látogatóid, olvasóbázisod lesz, új, a webáruházadra mutató linkek erősítik majd a profilodat, híred megy a közösségi médiában is.   Ha néhány hét után már megtérülést számolgatsz, felejtsd is el a blogolást. Olyan stratégia ez, amely a türelmeseket jutalmazza.   Webshop blog ötletek

10 tipp, hogy te is eredményesen blogolhass

  1. Készíts tartalomnaptárat. De légy egyben rugalmas is: ha aktuális eseményekről kell írnod, ha változnak közönséged preferenciái, ne félj belenyúlni a terveidbe. Módosítsd őket bátran az adatok alapján.
  2. Határozd meg a hatékonysági mutatókat. Tudnod kell, mit mérj. Nem mindegy, hogy számodra a látogatók vagy a leadek száma, a sikeres konverziók aránya vagy éppen a rád mutató hivatkozások jelentik a sikert. A KPI-okat világosan le kell fektetned már az elején.
  3. Használj analitikát és elemezz! Rendszeres időközönként nézd meg, hogy melyek azok a posztok, formátumok, időpontok, amelyek jobb eredményeket hoznak, és azokra erősíts!
  4. Jegyezd le az ötleteidet. A legjobb témaötletek véletlenszerűen jutnak eszedbe, gondoskodj róla, hogy ezeket egy helyen összegyűjtsd, és lehetőleg ne egy halom fecnin tedd ezt.
  5. Kifogytál a témákból? Dehogy fogytál. Nézz körül a szakmai oldalakon, a közösségi média releváns csoportjaiban. Használd a BuzzSumót, hogy lásd, mik a legnépszerűbb releváns témák, amelyeket feldolgozhatsz.
  6. Hangold össze blog, social és e-mail stratégiádat. Terjeszd a tartalmaidat minden lehetséges csatornán, hogy minél több embert érj el.
  7. Tedd könnyen elérhetővé. A felhasználó már a webshop nyitóoldaláról vagy akár bármely másik oldaláról könnyedén, egyetlen kattintással érje el a blog főoldalát, az aloldalakról pedig a releváns tartalmakat.
  8. Ne felejtsd a SEO-t. Soha ne a Google-nek írj, de mindig tartsd észben, hogy olyan témákat keress, amikre az emberek ténylegesen rákeresnek. Végezz kulcsszóelemzést, nézd át, milyen long tail kifejezéseket írnak be buyer personáid a keresőbe, és válaszold meg a kérdéseiket.
  9. Ne akarj magányos hős lenni. Ha egyedül akarsz megcsinálni mindet az írástól az analitikán és a dizájnon keresztül a terjesztésig, a tervezést, szervezést, bele fogsz rokkanni a feladatba. Ha kell, bízd meg alkalmas kollégáidat, ha szükséges, fizess meg profikat.

 

Ne add fel. Légy türelemmel és készíts egyre több és több minőségi anyagot. Hidd el: ha megépíted, eljönnek.

 

Magento projektek automata tesztelése Seleniummal

Ebből a cikkből kiderül, hogy: 

  • Miért a Selenium-ot válaszd?
  • Mikor éri meg a legjobban használni?
  • Hogyan és miből épül fel a Selenium?
  • Hogyan töltsd le?
  • Mik a funkciói? Hogyan használd?
  • A cikk legvégén pedig egy egyszerű példával mutatom be a folyamatot.

 

Tarts velem!

magento selenium teszter bea

 

Egy e-kereskedelmi vállalkozónak, napjaink kiélezett versenykörnyezetében, muszáj követnie a piacon történő változásokat, és ő maga is rendszeresen változtatásokat kell, hogy végezzen a honlapon.

Ezek a kisebb változtatások teszik lehetővé, hogy válaszoljon a legújabb trendekre és lépést tartson versenytársaival. Ez a változás lehet egy új termék, ajánlat, kedvezmények, szállítási módok, fizetési módok és még sok más.

A rendszeres módosítások közben, a cégek „zökkenőmentes” felhasználói élményt kell biztosítsanak, különben ez negatív és nem kívánt eredményekhez vezethet. Ennek érdekében a tesztelőknek folyamatosan ellenőrizniük kell a honlapot és kiszűrniük a hibákat. A webfejlesztő cégek agilis módszertan szerint dolgoznak, ahol a „sprint”-ek elég rövidek, és minden változtatás után szükséges átfogó, regressziós teszteteket végezni. Sajnos, amikor a változtatások túl gyakoriak, a manuális tesztelés monotonná válhat, és sok időt vehet igénybe. Előbb-utóbb valamiféle „könnyítést” kell alkalmaznunk.

 

Itt jön képbe a Selenium. Lássuk csak, miért.

 

 

1) Miért a Selenium?

 

SELENIUM:

A Selenium egy Firefox bővítmény, amely Black Box jellegű felületi tesztek készítésére ad lehetőséget. Ez annyit jelent, hogy az alkalmazás egészét teszteljük anélkül, hogy ismernénk annak belső működését. A tesztesetek felvétele a Selenium IDE Firefox plugin segítségével történik, amely automatikusan rögzíti a böngészőben végrehajtott műveleteket.

 

Ez nagyon megkönnyítheti a tesztelők életét, mert a tesztek gyorsan, de mégis  optimális módon fejlődnek és ez nagyon költséghatékony lehet. Továbbá a Selenium IDE egy fantasztikusan „segítőkész” eszköz, mert hatékonyan, gyorsan és robotszerűen hajt végre olyan lépéseket, melyek után az alkalmazás tesztelhetővé válik, és olyan állapotba segíti a szoftvert, amelyet manuálisan unalmas, fáradalmas elérni, tesztelni. Ha automatizálod az utat, mellyel eléred ezt a pontot, akkor nem leszel fáradt és ideges, illetve képes leszel továbbra is a tesztelési tudásod legjavát adni.

 

Az automata tesztek bevezetésének számtalan előnye van. Nézzünk is meg egy párat:

  • Lehetőség van a tesztesetek ismételt visszajátszására
  • A teszteseteket párhuzamosan hajthatjuk végre
  • Felügyelet nélkül is futtathatjuk a teszteket
  • Növeli a pontosságot és nagymértékben csökkenti az emberek által generált hibákat
  • Időt és pénzt takaríthatunk meg

 

Itt felmerül néhány fontos kérdés:

  • Mi a legjobb eszköz arra, hogy automatizáljam a teszteseteimet?
  • Milyen költségekkel jár?
  • Mennyire könnyű alkalmazni?

 

A fenti kérdések tekintetében, a webes alkalmazások esetében, ahogy fent is említettem, a Selenium egy nagyon jó megoldás. Miért?

  • Könnyen használható
  • Ingyenes (Open source)
  • Nagy felhasználói adatbázissal és segítő közösséggel rendelkezik
  • Több programozói nyelvet támogat

 

2) Mikor érdemes használni?

A Selenium IDE egy könnyen használható eszköz egyszerű tesztesetekre, de a komplexebbekre (ahol például több elágazás van) már kevésbé az.

 

A Selenium-os tesztek készítése akkor éri meg a leginkább, amikor a felület már nem nagyon módosul, de a mögöttes logika, szolgáltatások vagy a háttérrendszerek fejlesztése még folyamatban van. Selenium-os felületi tesztek készítésével a tesztek bármikor visszajátszhatók így szükség szerint folyamatosan értesülhetünk a tesztjeink lefutási eredményeiről!

 

3) Hogyan és miből épül fel?

A Selenium nemcsak egy egyedülálló eszköz, melyet csak egymagában lehet használni, sokkal inkább egy különböző teszteszközökből álló csomag.

Ez a csomag a következő komponensekből tevődik össze:

  • Selenium Integrated Development Environment (IDE) 01-selenium-ide-ikon
  • Selenium Remote Control (RC) 02-selenium-rc-ikon
  • Selenium WebDriver
  • Selenium Grid 03-selenium-grid-ikon

 

A felhasználó saját igényei szerint választhat ezek közül az eszközök közül.

04-selenium-csomag

 

  1. Selenium IDE

A Selenium IDE a legegyszerűbb a Selenium Csomag eszközei közül. A Rögzítés és a Visszajátszás funkció teszi kivételesen könnyűvé a megtanulását, hiszen semmilyen vagy csak minimális programozói hozzáértést igényel.

 

  1. Selenium RC (Selenium Remote Control)

A Selenium RC (Selenium 1) egy Java-ban írt eszköz, amely teszt script-ek létrehozását teszi lehetővé webes alkalmazásokhoz, a kívánt programozói nyelven, valamint ezek futtatását is elvégzi.

Ez az eszköz önmagában már nem igazán használatos.

 

  1. Selenium Grid

A Selenium Grid egy új funkciót tesz elérhetővé, mégpedig azt, hogy a Selenium

RC-ben írt teszteket egy időben több böngészőben és platformon futtathatjuk.

 

  1. WebDriver

A WebDriver egy különálló eszköz, de nagyon sok előnye van, főleg a Selenium RC-vel karöltve. A kettő összeolvadását gyakran Selenium 2-nek nevezik. A WebDriver közvetlenül eléri azokat a böngészőket, amelyek támogatják az automatizálást.

 

Támogatott böngészők:

05-selenium-tamogatott-bongeszok

 

Támogatott programozói nyelvek:

06-selenium-tamogatott-programnyelvek

 

Támogatott operációs rendszerek:

07-selenium-tamogatott-operacios-rendszerek

 

4) Hogyan töltsd le?

 

Első lépés:

Nyisd meg a böngészőt (Firefox) és írd be a Seleniumhq URL-t. Ez a hivatalos oldala a Selenium-nak. Nyomj a „Download” fülre, itt megtalálod a Selenium összes eddigi verzióját.

08-selenium-letoltes

 

Második lépés:

A Selenium IDE bejegyzés alatt találsz egy linket, melyre rákattintva a Firefox bővítmény oldalára kerülsz, ahonnan azonnal letölthető és telepíthető.

09-selenium-letoltes-telepites

 

Harmadik lépés:

Klikkelj az „Add to Firefox” gombra.

10-selenium-ide-firefox

 

Negyedik lépés:

Klikkelj a „Telepítés” gombra.

11-selenium-telepitese

 

Ezzel készen is vagyunk, már meg is jelent a böngésződben a jobb felső sarokban a Selenium IDE ikonja. Innen tudod később indítani a programot. Ha ráklikkelsz már meg is jelenik a Selenium ablak.

 

12-selenium-telepites-ide

 

5) A Selenium funkciói és használata

 

Nézzük meg részletesen a menüpontokat és a funkciókat.

Sajnos a Selenium kisebb hibái közé tartozik, hogy egyes elnevezések magyarul, mások pedig angolul jelennek meg, de a csatolt képeken jól látszik minden, remélem így is érthető lesz mindenki számára.

 

1) Menüsor

A menüsor az Selenium ablak felső részén van elhelyezve. Öt részből áll:

  • Fájl
  • Szerkesztés
  • Actions (Műveletek)
  • Options (Opciók)
  • Súgó

 

1.1 Fájl menü

A Fájl menü nagyon hasonló bármely más alkalmazásban használt menühöz. Lehetőséget nyújt a felhasználó számára, hogy:

  • Új tesztesetet hozzon létre, meglévőt kinyisson, vagy elmentse a jelenlegit.
  • Kiexportálhasson egy adott tesztesetet vagy egy teszteset csoportot, a kívánt programozási nyelven, az „Export Test Case As” és „Export Test Suite As” opciókkal. Az első a jelenleg aktív tesztesetet exportálja, míg a második az összes nyitva lévőt.
  • Bezárhassa a tesztesetet

 

13-selenium-menu-fajl

 

A Selenium IDE tesztesetek a következő formátumban menthetőek el:

  • HTML formátum

 

A Selenium IDE tesztesetek a következő formátumban/programozási nyelven exportálhatók:

  • java (Java-ban exportálva)
  • rb (Ruby-ban exportálva)
  • py (Python-ban exportálva)
  • cs (C#-ben exportálva)

 

1.2.  Szerkesztés menü

14-selenium-menu-szerkesztes

 

A Szerkesztés menü olyan opciókat biztosít, mint a Visszavonás, Újra, Kivágás, Másolás, Beillesztés, Törlés és a Minden kijelölése, amelyek általában minden Szerkesztés menüben jelen vannak, valamint ezek mellet még a következők állnak rendelkezésünkre:

  • Insert New Command (Új parancs beszúrása) ‒ ez lehetővé teszi a felhasználó számára, hogy a teszteseten belül felvegyen egy új parancsot
  • Insert New Comment (Új hozzászólás beszúrása) ‒ ez lehetővé teszi a felhasználó számára, hogy a teszteseten belül beszúrjon egy hozzászólást, amivel a későbbi parancsokat magyarázza.

 

Insert New Command

Az új parancs a kijelölt parancs/lépés fölé kerül.

14-selenium-uj-parancs

 

A felhasználó most beszúrhatja az új parancsot, kitöltve a Target (Cél) és Value (Érték) mezőket.

15-selenium-uj-parancs-target-value

 

Insert New Comment

Az új hozzászólás ugyanúgy kerül be, mint a parancs.

16-selenium-uj-hozzaszolas

 

A lila szín jelöli, hogy ez egy hozzászólás/magyarázat.

 

1.3. Actions (Műveletek) menü

17-selenium-muveletek-actions-menu

 

 

 

Az Actions menüben a következő lehetőségek közül választhatunk:

 

  • Record (Rögzítés) – Erre a gombra kattintva a Selenium IDE el kezdi rögzíteni a Firefox böngészőben végrehajtott lépéseket.
  • Play entire test suite – Ez az opció lefuttatja az ehhez a teszt csoporthoz tartozó összes tesztesetet.
  • Play current test case – Ez az opció a jelenleg aktív, kijelölt tesztesetet futtatja le.
  • Pause/Resume – A teszteset futását szüneteltetni és folytatni is lehet.
  • Toggle Breakpoint – A felhasználó beszúrhat egy vagy akár több töréspontot a tesztbe, így bármely lépésnél leállásra kényszerítheti a tesztet.
  • Set/Clear Start Point – Itt egy kezdő pontot adhatunk meg, bármelyik lépéshez, ezután innen fog indulni a teszt.
  • Ugyanitt tudjuk állítani a teszt futásának sebességét is a „Faster, „Fastest”, „Slower” és a „Slowest opciókkal.

 

1.4 Options (Opciók) menü

18-selenium-opciok-options-menu

 

Az Options menü lehetőséget ad a felhasználónak, hogy gyakorolhassa a Selenium IDE által támogatott beállítások kezelését, „játszadozhat” az általános beállításokkal (General), az elérhető formátumokkal (Formats), az elérhető bővítményekkel (Plugins) és az elérhető helymeghatározókkal és azok sorrendjével (Locator Builders).

 

19-selenium-opciok-options-menu-general-altalanos

 

1.5. Súgó menü

 

Ha elakadtunk, itt nézhetjük meg a dokumentációkat, itt jelenthetjük be, ha hibát észleltünk és innen tudunk eljutni a Selenium hivatalos weboldalára vagy blogjára.

 

 

2) Az URL sáv

 

Az URL sáv ugyanolyan és nagyjából úgy működik, mint egy böngészőben. Megjegyzi a korábban látogatott oldalak címét, így később csak ki kell választanunk, ami nagyban megkönnyíti a dolgunkat, főleg, ha már nem tudjuk megjegyezni és észben tartani mindegyik weboldal címét, amin dolgozunk.

20-selenium-url-sav

 

3) Eszköztár

 

21-selenium-eszkoztar

 

Az eszköztár közvetlenül az URL sáv alatt található és a következő opciókat tartalmazza:

 

  • Playback Speed – Itt lehet állítani a teszt futtatásának sebességét. 22-selenium-eszkoztar-playback-speed
  • Play test suite – Ezzel a gombbal az összes tesztesetet futtathatjuk, ami jelen éppen meg van nyitva. 23-selenium-eszkoztar-play-test-suite
  • Play test case – Ezzel a gombbal az éppen aktív tesztesetet futtathatjuk. 24-selenium-eszkoztar-play-test-case
  • Pause – Szüneteltethetjük a teszt futását. 25-selenium-eszkoztar-pause
  • Step – A felhasználó átléphet egy adott parancsot, egyszerre csak egy parancsot végrehajtva. Általában hibajavításnál használatos. 26-selenium-eszkoztar-step
  • Rollup – Összevonhatunk két vagy több lépést. 27-selenium-eszkoztar-rollup
  • Record – Ezzel a gombbal indíthatjuk és le is állíthatjuk a rögzítést. 28-selenium-eszkoztar-record

 

4) Szerkesztő

 

A szerkesztő az az ablak, ahol a teszteset lépéseit láthatjuk, ide kerülnek be az újonnan rögzített parancsok is. A szerkesztőnek két nézete van:

 

1) Táblázat (Table) nézet

29-selenium-tablazat

 

2) Forrás (Source) nézet

Itt a teszteset HTML formátumban jelenik meg.

30-selenium-forras-nezet

 

A Szerkesztőben a felhasználónak lehetősége nyílik begépelni a parancsokat, a Selenium már az első karakter begépelése után felajánlja a lehetséges opciókat.

A Select gombra kattintva kiválaszthatjuk az adott elemet közvetlenül a böngészőből, úgy, hogy ráklikkelünk.

A Find gomb az adott érték alapján rámutat a hozzá tartozó elemre a böngészőben.

31-selenium-find

 

5) Test Case ablak

32-selenium-test-case-ablak

 

Itt fog megjelenni a legújabb teszteset, de mivel a Selenium-ban lehetőség van arra, hogy egyszerre több tesztesetet is megnyissunk, az összes itt lesz felsorolva. Így könnyen kiválaszthatjuk, hogy éppen melyiken szeretnénk dolgozni. Egy teszt-re kattintva a Szerkesztő ablakban megjelennek annak lépései.

Itt jelennek meg a lefuttatott teszt eredményei is, amit a Selenium IDE két színnel jelöl: a pirossal és a zölddel.

  • A Piros szín a hibákat illetve a teszt elbukását jelöli
  • A Zöld szín a sikeres tesztesettel társítható

 

6) Log (Napló) ablak

33-selenium-naplo-log-ablak

 

A Log fülön információkat láthatunk, üzenetek formájában, mindegyik lépésről külön-külön, annak lefutásának pillanatában. Itt jelenik meg az is, ha hiba van a tesztben vagy elbukott, piros színnel jelölve, így a későbbiekben könnyen javíthatjuk a tesztünket.

Itt találhatjuk, többek között a Reference fület is, ahol részletes leírást kapunk egy adott parancsról, és az Expert fület is, amelyet egy kiegészítő ad hozzá a Seleniumhoz. Itt tippek és javítási ötletek jelennek meg, amelyeket azonnal elvégezhetünk az Inspect, majd Fix gombra való kattintással.

Ezzel nagyjából elérkeztünk a bemutatás és az elméleti rész végéhez. Egy rövidebb példával zárnám a cikkemet, hogy gyakorlatban is lássuk a fent leírtakat. Ez tényleg egy egyszerű teszteset, ennél jóval összetettebbekkel is össze lehet állítani, de nem szerettem volna túlbonyolítani, hisz nem ez a cikkem lényege.

 

Példa

 

Egy teszt „script” összeállítása 3 nagy lépésből áll:

  1. Rögzítés – A Selenium IDE rögzít minden kattintást és műveletet, amit a böngészőben hajtottunk végre.
  2. Visszajátszás – A már összerakott tesztesetet többször vissza kell játszanunk, hogy meggyőződjünk arról, hogy jól működik, esetleg javítsuk, csiszoljunk rajta.
  3. Mentés – Ha a teszt megfelelően stabil, ajánlott elmenteni, hogy a jövőben bármikor újra használhassuk.

 

Rögzítés

 

Első lépés

Indítsuk el a Firefox böngészőt és ezzel együtt a Selenium-ot is, az eszköztárban található ikonra kattintva.

 

Második lépés

Írjuk be a tesztelni kívánt weboldal címét (https://accounts.google.com) az URL sávba.

34-selenium-url-tesztelesre

 

Harmadik lépés

A Record gomb alapértelmezetten be van kapcsolva, ha mégsem, ne felejtsük el bekapcsolni, különben nem rögzíti a lépéseket.

 

Negyedik lépés

Nyissuk meg az adott weboldalt (accounts.google.com) Firefox-ban.

 

35-selenium-google-bejelentkezes

 

 

Ötödik lépés

Írjunk be egy létező és valós e-mail címet.

 

Hatodik lépés

Írjuk be az e-mail címhez tartozó jelszót.

 

Hetedik lépés

Kattintsunk a „Sign In” vagy „Bejelentkezés” gombra, a belépés befejezéséhez.

 

Nyolcadik lépés

Végül leállíthatjuk a rögzítést a piros gombbal, és visszajátszhatjuk a tesztesetünket.

 

Figyelem! Ha a tesztesetbe nem rögzítettük bele a Kijelentkezés lépést, a visszajátszás előtt midnenképp szükséges ezt a lépést megtenni, különben a Selenium nem fogja megtalálni a szükséges mezőket vagy gombokat.

 

Visszajátszás

Most, hogy elkészült a tesztünk, szükséges visszajátszani, hogy megnézzük, elég stabil-e. Kattintsunk a „Play current test case” gombra.

 

36-selenium-replay

 

Mentés

Ha minden rendben van, elmenthetjük a script-et, a Fájl -> Mentés másként opcióra kattintva.

 

ÖSSZEGZÉS

Egyértelmű cél, hogy weboldalunk minőségét fokozzuk és pozitív felhasználói élményt nyújtsunk. Ám ezentúl fontos szempont az is, hogy az ezen dolgozó emberek szeressék és élvezzék a munkájukat, hogy a legjobbat nyújthassák.

Összesítésben a Selenium egy nagyon hasznos eszköz, amivel megkönnyíthetjük a tesztelőink munkáját, mellyel jobb minőséget érhetnek el. Ahogy a fentiekből is kiderül, nagyon egyszerű a használata, de sok gyakorlást igényel, ha komplexebb feladatokat kívánunk megoldani.

 

Magento 2 modul fejlesztés lépésről lépésre – 1. rész

Ebben a cikkben a következő témákat öleljük fel:

 

1) Követelmények, dokumentumok és Magento 2.0 installálás

A legfőbb dokumentum források a Magento 2.0 rendszerrel kapcsolatban az alábbi két linken érhetők el: http://devdocs.magento.com/ https://github.com/magento/magento2 A Magento 2.0 telepítése előtt a fejlesztői környezetünk minimális beállításait el kell végeznünk, hogy a rendszert telepíteni tudjuk. Részletes információt ezen a linken olvashatunk: http://devdocs.magento.com/guides/v2.0/install-gde/bk-install-guide.html Már elsőre láthatóak az alapvető követelmények:

  • PHP 5.5.x és ennél nagyobb verziók
  • MySQL 5.6.x és ennél nagyobb verziók

Amennyiben nem rendelkezünk ezekkel, a telepítés megkezdése előtt frissíteni kell lokális fejlesztői környezetünket. Az installálás során három módszert tudunk alkalmazni. Az első esetben egyszerűen letöltjük a Magento hivatalos oldalról a két elérhető verziót. Az egyik csak az alap rendszert tartalmazza, míg a másik példa adatokkal is ellátja az installálás során a rendszert (Sample Data). A két csomag itt érhető el: https://www.magentocommerce.com/download Második telepítési lehetőség, amikor a Github-ról klónozzuk a Magento rendszert. Ennek előnye, hogy folyamatosan frissíteni tudjuk a Git segítségével a fejlesztőkörnyezetünkben. Fontos itt megjegyezni, hogy a Git klónozás során a develop branch-et kapjuk meg, így ahogy a klónozás befejeződött, célszerű átváltani (git checkout) a 2.0 vagy a merchant_beta branch-ekre. A Magento 2.0 Github elérés: https://github.com/magento/magento2   A harmadik lehetőség a Metapackage installáció. Ebben az esetben a letöltött package után a composer segítségével installáljuk a Magento 2.0 rendszert, vagyis az alap Magento modulokat a hivatalos Magento Repo-ból kapjuk, az egyéb összetevőket más repo-ból. Metapackage installálási útmutató: devdocs.magento integrator install

2) Célszerű IDE beállítások a fejlesztés megkezdése előtt

Miután sikeresen installáltuk a Magento 2.0 rendszert fejlesztőkörnyezetünkbe és az megfelelőn működik, célszerű beállítani a fejlesztéshez használt IDE-t, ami e cikk esetében a Jetbrains PhpStorm-ra vonatkozik. A Magento 2.0 az alábbi technológiákat alkalmazza működése és fejlesztése során: devdocs magento tech stack Első lépésben a projekt megnyitása után a PhpStorm-ban célszerű lefuttatni a Detect PSR-0 Namspace Roots parancsot, ami a menüből elérhető, de egyébként az IDE ezt automatikusan fel fogja ajánlani. Következő lépés a megfelelő PHP CodeSniffer beállítása. Alapból az IDE a PSR-1 és PSR-2 coding standard-ot támogatja, így itt célszerű a PSR-2-őt választani, de ajánlottabb a PSR-4 alkalmazása (azonban ez alapból nincs a PhpStorm-ban). Az IDE-ben ezen beállítások elvégzésére a cikk nem tér ki. Amennyiben megnyitunk a vendor/magento könyvtárban lévő bármely modulhoz tartozó etc/module.xml fájlt (vagy egyéb itt található xml fájlt), láthatjuk, hogy az URN feloldás jelenleg sikertelen. A Magento 2.0 számos fejlesztői parancsot tartogat számunkra, ami a fejlesztés során segítségünkre lehet. Ezen parancsok listáját így tekintethetjük meg:

  • belépünk a bin könyvtárba a terminálunkban
  • kiadjuk a „php magento” parancsot
  • ennek eredményeképpen kilistázódik az összes lehetséges parancs, amit a fejlesztés során használhatunk és igen hasznos

Visszatérve az URN feloldási problémára, ezt az alábbi paranccsal tudjunk megoldani: php bin/magento dev:urn-catalog:generate .idea/misc.xml (látható, hogy ezt nem a bin könyvtárból futtatjuk, hanem root könyvtárból) A fejlesztés megkezdése előtt kikapcsoljuk a teljes cache-t (admin felületről, vagy fent említett php magento parancsok segítségével). Amennyiben Apache szerver alatt fejlesztünk, a root könyvtárban található .htaccess fájlban beállítjuk a developer módot: SetEnv MAGE_MODE developer Célszerű a fejlesztői környezetünkben az xdebug-ot kikapcsolni (php.ini beállítás), mert ez jelentősen lassítja a rendszert.

3) Alap modul felépítése és elkészítése

Amint installáltuk a Magento 2.0-át (és nem Github-ról klónoztuk), szembesülünk azzal, hogy a jól megszokott app/code könyvtár hiányzik és minden alap Magento modul a vendor/magento könyvtárban van elhelyezve. Ettől nem kell megijedni, a fejlesztés első lépése, hogy létrehozzuk az app/code könyvtárat. Miután ez elkészült, létrehozzuk a modulunk alap könyvtárát (Vendor/Module). Ez a mi esetünkben az Aion/Test könyvtár lesz. Következő lépésben két könyvtárat hozunk létre:

  • app/code/Aion/Test/etc
  • app/code/Aion/Test/Helper

A modul alap definíciós fájlja az app/code/Aion/Test/etc könyvtárba kerül elhelyezésre, melynek neve module.xml.

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Aion_Test" setup_version="2.0.0">
        <sequence>
            <module name="Magento_Store"/>
        </sequence>
    </module>
</config>

A fájlban definiáljuk a modul nevét (Vendor_Module -> Aion_Test), a verzió számát, ami jelen esetben 2.0.0 és a függőségeket, ami jelenleg csak az alap Magento_Store modul. A következő lépésben létrehozzuk a Helper fájlt. Az alap helper fájl az app/code/Aion/Test/Helper/Data.php lesz. Nézzük a tartalmát:

<?php
/**
 * Copyright © 2015 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Helper;

/**
 * Aion Test helper
 */
class Data extends \Magento\Framework\App\Helper\AbstractHelper
{
    /**
     * Path to store config if extension is enabled
     *
     * @var string
     */
    const XML_PATH_ENABLED = 'aion/basic/enabled';

    /**
     * Check if extension enabled
     *
     * @return string|null
     */
    public function isEnabled()
    {
        return $this->scopeConfig->isSetFlag(
            self::XML_PATH_ENABLED,
            \Magento\Store\Model\ScopeInterface::SCOPE_STORE
        );
    }
}

A helper fájlt elég üresen is létrehozni, de mi már egy alap függvényt implementáltunk (public function is Enabled()) és egy konstanst. A konstansban meghatározott string (útvonal) később a modulhoz tartozó admin konfigurációs fájlban fog értelmet kapni (system.xml). Következő lépésben elhelyezzük a modul könyvtárba (app/code/Aion/Test) a registration.php fájlt, ami a modul felismerését hivatott elvégezni a Magento 2.0 rendszerben. Nézzük ennek tartalmát:

<?php
/**
 * Copyright © 2015 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */

\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Aion_Test',
    __DIR__
); 

A file tartalmát bármelyik alap Magento 2.0 modulból átmásolhatjuk, a lényeg, hogy a második paraméternél a saját modulunk szerepeljen (Vendor_Modul -> Aion_Test). Mivel az alap Magento 2.0 modulok is composer-t használnak a frissítésre, mi is elkészítjük saját composer.json fájlunkat. Ennek tartalma így néz ki:

{
    "name": "aion/module-test",
    "description": "N/A",
    "require": {
        "php": "~5.5.0|~5.6.0|~7.0.0",
        "magento/module-config": "100.0.*",
        "magento/module-store": "100.0.*",
        "magento/module-backend": "100.0.*",
        "magento/framework": "100.0.*"
    },
    "type": "magento2-module",
    "version": "100.0.2",
    "license": [
        "OSL-3.0",
        "AFL-3.0"
    ],
    "autoload": {
        "files": [
            "registration.php"
        ],
        "psr-4": {
            "Aion\\Test\\": ""
        }
    }
}

A composer.json tartalmazza modulunk alap információt, függőségeket, licenc, verzió stb. adatokat. Már szinte el is készültünk, de még két fájl hiányzik egy szépen felépített modulhoz. Ez a két fájl, mely szintén a modulunk könyvtárában (app/code/Aion/Test) kap helyet: README.md és COPYING.txt. Az előbbi a Git leírás, míg az utóbbi a licenc információkat tartalmazza, ami jelen esetben OSL 3.0. Tehát így néz ki a modulunk felépítése: app/code Aion/ Test/ etc/module.xml Helper/Data.php composer.json COPYING.txt README.md registration.php   A következő lépésben aktiválni kell a modult, és megvizsgálni, hogy a Magento 2.0 rendszer felismerte-e megfelelően. A Magento 1.x rendszerben ez a frontend vagy admin oldal frissítéssel történt meg, azonban a 2.0-ban a korábban már említett parancsokat kell alkalmaznunk. A terminálban belépünk a /bin könyvtárba, majd kiadjuk a php magento module:status parancsot. Ha mindent jól végeztünk el, akkor listázásra kerül az engedélyezett (Enabled) modulok listája és még nem aktív vagy nem engedélyezett (Disabled) modulok listája. Utóbbi helyen jelenik meg a saját modulunk is: Aion_Test. Amennyiben nem jelenik meg, a modulunk felépítése során valamit elrontottunk. Ezután kiadjuk a terminálban /bin könyvtárban a php magento module:enable Aion_Test parancsot. Ekkor megjelenik, hogy a modult a rendszer felismerte és engedélyezte. Ez valójában azt jelenti, hogy az app/etc/config.php fájlban lévő tömbbe bekerült az ’Aion_Test’ => 1 bejegyzés. Ezután értesítést kapunk, hogy a modult adatbázis szinten is „regisztráljuk” be. Kiadjuk a terminálban a php magento setup:upgrade parancsot. A parancs végrehajtása során a Magento 2.0 végigfut az összes alap modulon és egyedi modulon és update-eli a rendszert, vagyis lefuttatja a séma és adat scripteket, amennyiben nagyobb verzió számot talál, mint a jelenlegi. Mindez a mi modulunk esetében az jelenti, hogy korábban leírt 2.0.0 verzió szám beírásra kerül a setup_module táblába. Ha ezután belépünk az admin felületre és kiválasztjuk Store / Configuration menüpontot, majd ezt követően az Advanced / Advanced aloldalt, akkor megjelenik a modulunk a listában enabled állapotban. Ezzel el is készültünk az alap modulunkkal.

4) Modul admin konfiguráció és jogosultság kezelés

Miután az alap modulunk elkészült, felépítjük a hozzá tartozó admin konfigurációt és jogosultság kezelést. Első lépésben a konfigurációhoz szükséges fájlt hozzuk létre, ami az app/code/Aion/Test/etc/adminhtml könyvtárban lesz elhelyezve és a neve system.xml.

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <tab id="aion_tab" translate="label" sortOrder="500">
            <label>Aion Extensions</label>
        </tab>
        <section id="aion" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="1">
            <label>Test Extension</label>
            <tab>aion_tab</tab>
            <resource>Aion_Test::test</resource>
            <group id="basic" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>Basic</label>
                <field id="enabled" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Enable Extension</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
            </group>
            <group id="more" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>More</label>
                <field id="variable" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Some Variable</label>
                </field>
            </group>
        </section>
    </system>
</config>

A fájlban definiálunk egy <tab> tag-et is, így a modulunk saját menüpontban(Aion Extensions -> Test Extension) fog megjelenni az admin Stores / Configuration aloldalán. Ezen belül implementálunk két tab-ot (basic és more id-kkal). Az elsőbe helyezzük el az alap No / Yes select típusú konfigurációs változót, amit a modul engedélyezésére és tiltására használunk. Lekérdezését már korábban implementáltuk a Data.php file-ban(Helper/Data.php). A példa kedvéért létrehozunk egy text típusú konfigurációs változót is Variable névvel későbbi használatra. A fájlban még fontos rész a <resource> tag is, aminek a jogosultság kezelés implementálásakor lesz szerepe. Ez a mi esetünkben Aion_Test::test.   A következő lépésben létrehozzuk a jogosultság kezeléshez szükséges fájlt is, ami az app/code/Aion/Test/etc könyvtárba kerül acl.xml néven. Ennek tartalma:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
    <acl>
        <resources>
            <resource id="Magento_Backend::admin">
                <resource id="Magento_Backend::stores">
                    <resource id="Magento_Backend::stores_settings">
                        <resource id="Magento_Config::config">
                            <resource id="Aion_Test::test" title="Aion Test Extension Section" />
                        </resource>
                    </resource>
                </resource>
            </resource>
        </resources>
    </acl>
</config>

Amint ezzel készen vagyunk, leellenőrizzük, hogy megfelelően létrejött-e a modulhoz tartozó jogosultság beállítás a Role Resource (Admin felhasználó szerepek) részben. Az admin felületen megnyitjuk a System / User Roles menüpontot, majd itt bármelyik adminisztrátori csoportot (alapból egy van) megnyitva a Role Resouces almenüt kiválasztva, majd a Resource Access-nél a Custom lehetőség alatt megvizsgáljuk, hogy a fában megjelenik-e a saját modulunk.   Ezt követően leellenőrizzük a korábban kialakított konfigurációs menüpont, és hogy tartalma is megjelenik-e. Az adminban a Stores / Configuration-t választva a bal menüben megnézzük, hogy megjelent-e a saját menüpontunk (Aion Extensions) és annak taralma (Test Extension). Az alap konfigurációs értékeket az app/code/Aion/Test/etc könyvtárban lévő config.xml fájlban határozzuk meg:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
    <default>
        <aion>
            <basic>
                <enabled>1</enabled> 
            </basic>
        </aion>
    </default>
</config>

Jól látható, hogy a file-ban látható tag-ek megfelelnek a system.xml-ben lévő saját id-kkal.

5) Alap frontend controller, block, layout és template

A következőkben létrehozunk egy saját frontend controller-t, a hozzátartozó layout és router konfigurációt. Majd ezt követően egy template fájlt és a hozzá tartozó block-ot is implementáljuk. Elsőként elkészítjük a controller fájlt, ami valójában egy Action-t jelent. Ezt az app/code/Aion/Test/Controller/Index/ könyvtárban lévő Index.php-val implementáljuk. A fájl tartalma:

<?php
/**
 * Copyright © 2015 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Controller\Index;

use Magento\Framework\App\Action\Context;
use Magento\Framework\View\Result\PageFactory;
use Magento\Framework\Controller\Result\ForwardFactory;
use Magento\Framework\Exception\NotFoundException;
use Magento\Framework\App\RequestInterface;

/**
 * Contact index controller
 */
class Index extends \Magento\Framework\App\Action\Action
{
    /**
     * @var PageFactory
     */
    protected $resultPageFactory;

    /**
     * @var ForwardFactory
     */
    protected $resultForwardFactory;

    /**
     * @var \Aion\Test\Helper\Data
     */
    protected $helper;

    /**
     * Index constructor.
     *
     * @param \Magento\Framework\App\Action\Context $context
     * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
     * @param \Magento\Framework\Controller\Result\ForwardFactory $resultForwardFactory
     * @param \Aion\Test\Helper\Data $helper
     */
    public function __construct(
        Context $context,
        PageFactory $resultPageFactory,
        ForwardFactory $resultForwardFactory,
        \Aion\Test\Helper\Data $helper
    ) {
        $this->resultPageFactory = $resultPageFactory;
        $this->resultForwardFactory = $resultForwardFactory;
        $this->helper = $helper;
        parent::__construct($context);
    }

    /**
     * Dispatch request
     *
     * @param RequestInterface $request
     * @return \Magento\Framework\App\ResponseInterface
     * @throws \Magento\Framework\Exception\NotFoundException
     */
    public function dispatch(RequestInterface $request)
    {
        if (!$this->helper->isEnabled()) {
            throw new NotFoundException(__('Page not found.'));
        }
        return parent::dispatch($request);
    }

    /**
     * Aion Test Page
     *
     * @return \Magento\Framework\View\Result\Page
     */
    public function execute()
    {
        /** @var \Magento\Framework\View\Result\Page $resultPage */
        $resultPage = $this->resultPageFactory->create();
        $resultPage->getConfig()->getTitle()->set(__('Aion Test Page'));
        if (!$resultPage) {
            $resultForward = $this->resultForwardFactory->create();
            return $resultForward->forward('noroute');
        }
        return $resultPage;
    }
} 

A fájlban három fontos függvény található, mindhárom a szülőosztályban van definiálva. A __construct függvényt használjuk arra, hogy az általunk használni kívánt egyéb osztályokat injektáljuk (pl. helper osztály, dependency injection).  A dispatch függvény automatikusan le fog futni a __construct után. Itt vizsgáljuk meg, hogy a modulunk engedélyezve van-e, vagy sem és megfelelően lekezeljük. Végül pedig az execute függvény jelenti magát az action-t, ami a jelenlegi esetben az Index action-nek felel meg. A működése során a $resultPageFactory objektumot létrehozzuk (ami a későbbi layout konfiguráció alapján felépíti az oldalt). Az objektum setConfig függvényével beállítunk egy oldal title-t. Ezután megvizsgáljuk, hogy az objektum létrejött-e, vagy sem. Amennyiben nincs hiba, visszatérünk a $resultPage objektummal, ellenkező esetben noroute (vagyis 404-es) oldalra irányítjuk az action-t.   Létrehozzuk az app/code/Aion/Test/etc/frontend könyvtárban a routes.xml fájlt, amiben meghatározzuk, hogy milyen url alatt szeretnénk meghívni a modulunkhoz controller-eket. A fájl tartalma:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="standard">
        <route id="test" frontName="test">
            <module name="Aion_Test" />
        </route>
    </router>
</config>

A router konfigurációban látható, hogy a modulunkhoz a „test” url-t rendeltük, vagyis ez esetben a {{base_url}}test/index/index fogja meghívni a fent részletezett controller-t és azon belül az execute függvényt (Index action). Természetesen index controllert és action-t nem kell megadni mindig az url-ben, a {{base_url}}test/ is ugyanerre a helyre fog minket irányítani.   Következőkben létrehozzuk a layout fájlt ami az app/code/Aion/Test/view/frontend/layout könyvtárban kap helyet test_index_index.xml néven. Jól látható, hogy a fájl neve követi a router -> controller -> action neveket. A fájl tartalma:

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2015 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <head>
        <title>Aion Test Page</title>
    </head>
    <body>
        <referenceContainer name="content">
            <block class="Aion\Test\Block\Test" name="testPage" template="Aion_Test::test.phtml" />
        </referenceContainer>
    </body>
</page>

A layout file <head> részében beállítjuk az oldal alap címét, a tartalmi részben (content) egy block-ot és a hozzá tartozó template fájlt is definiáljuk. A block-ot az app/code/Aion/Test/Block könyvtárban lévő Test.php-ban valósítjuk meg. A fájl tartalma:

<?php
/**
 * Copyright © 2015 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Block;

use Magento\Framework\View\Element\Template;

/**
 * Aion Test Page block
 */
class Test extends Template
{
    /**
     * @param Template\Context $context
     * @param array $data
     */
    public function __construct(Template\Context $context, array $data = [])
    {
        parent::__construct($context, $data);
    }

    /**
     * Test function
     *
     * @return string
     */
    public function getTest()
    {
        return 'This is a test function for some logic...';
    }
}  

Észrevehetjük, hogy a példában használt __construct függvényben nem deklaráltunk semmit, így ez lényegében el is hagyható. Azonban célszerű már az elején megvalósítani, ha például később egy storeManager vagy Helper-t vagy bármi mást is szeretnénk implementálni.   A template file az app/code/Aion/Test/view/frontend/templates könyvtárban kap helyet test.phtml néven. A fájl tartalma:

<?php
/**
 * Copyright © 2015 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
?>

<?php
/**
 * @var $block \Aion\Test\Block\Test
 */
?>

<p><?php echo $block->getTest(); ?></p>
<p><?php echo __('This is a text.') ?></p>

A template file a példa kedvéért meghívja a Block-ban definiált teszt függvényt, és emellett egy egyszerű string-et is kiír, ami egy translate függvénybe van ágyazva. Ha mindennel elkészültünk, a modulunk így néz ki: app/code Aion/ Test/ Block/Test.php Controller/Index/Index.php etc/adminhtml/system.xml /frontend/routes.xml acl.xml config.xml module.xml Helper/Data.php view/frontend /layout/test_index_index.xml /templates/test.phtml composer.json COPYING.txt README.md registration.php     Az előzőekben megismerkedtünk egy alap Magento 2.0 modul elkészítésével és felépítésével. Most folytatjuk, és megismerkedünk hogyan készíthetünk saját adatbázis táblát, tölthetjük fel alapadatokkal a modulunkhoz, és a táblaadatok kezeléséhez szükséges modellekkel, ill. collection-nel. Emellett a frontend-en megnézzük, hogy érhetőek el tábla adatok és milyen módon jeleníthetjük meg őket.

6) Modulhoz tartozó adatbázis tábla elkészítése – 1

A modulhoz tartozó adatbázis táblát egy a Magento 1.x-ből már ismert installer script segítségével készíthetjük el, azonban a file neve és felépítése kicsit eltér.   Az adatbázis táblát az app/code/Aion/Test/Setup könyvtárban lévő InstallSchema.php-ban valósítjuk meg. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */

namespace Aion\Test\Setup;

use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\DB\Adapter\AdapterInterface;

/**
 * @codeCoverageIgnore
 */
class InstallSchema implements InstallSchemaInterface
{
    /**
     * Install table
     *
     * @param \Magento\Framework\Setup\SchemaSetupInterface $setup
     * @param \Magento\Framework\Setup\ModuleContextInterface $context
     * @throws \Zend_Db_Exception
     */
    public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
        $installer = $setup;

        $installer->startSetup();
        /**
         * Create table 'aion_test'
         */
        $table = $installer->getConnection()->newTable(
            $installer->getTable('aion_test')
        )->addColumn(
            'test_id',
            \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT,
            null,
            ['identity' => true, 'nullable' => false, 'primary' => true],
            'Test ID'
        )->addColumn(
            'name',
            \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
            255,
            ['nullable' => false],
            'Test Name'
        )->addColumn(
            'email',
            \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
            255,
            ['nullable' => false],
            'Test Email'
        )->addColumn(
            'creation_time',
            \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
            null,
            ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT],
            'Test Creation Time'
        )->addColumn(
            'update_time',
            \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
            null,
            ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT_UPDATE],
            'Test Modification Time'
        )->addColumn(
            'is_active',
            \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT,
            null,
            ['nullable' => false, 'default' => '1'],
            'Is Test Active'
        )->addIndex(
            $setup->getIdxName(
                $installer->getTable('aion_test'),
                ['name', 'email'],
                AdapterInterface::INDEX_TYPE_FULLTEXT
            ),
            ['name', 'email'],
            ['type' => AdapterInterface::INDEX_TYPE_FULLTEXT]
        )->setComment(
            'Aion Test Table'
        );
        $installer->getConnection()->createTable($table);

        $installer->endSetup();
    }
}

A script létrehozza a modulhoz tartozó példa táblát, aminek neve „aion_test” lesz. Ezután létrehozza az alábbi mezőket is:

  • test_id – primary key, smallint (6)
  • name – varchar (255)
  • email – varchar (255)
  • creation_time – timestamp
  • update_time – timestamp
  • is_active – smallint (6)

Ezt követően a scriptben célszerű és erősen ajánlott index-szel (addIndex) ellátni azon text (text és varchar és egyéb, ha szükséges) típusú oszlopokat, melyekben keresni fogunk a későbbiekben vagy a leendő collection-t szűrni fogjuk. A példa esetében ez a két mező a name és az email.

7) Modulhoz tartozó adatbázis tábla elkészítése – 2

Korábban már adtunk verziószámot a modulunkhoz az app/code/Aion/Test/etc könyvtárban lévő module.xml segítségével ‒ amiben meghatároztuk az alap verziót, majd a modul engedélyezése (terminál parancsok) segítségével ez a verziószám bekerült a „setup_module” Magento 2.0 táblába. Arra, hogy fent elkészített script lefusson, két lehetőségünk van: növeljük a verziószámot a modulunkban, vagy töröljük a modulunkhoz tartozó korábbi verziószám bejegyzést. Mivel még csak a modul fejlesztésének elején tartunk, így célszerű törölni „setup_module” Magento 2.0 táblából a modulunkhoz tartozó bejegyzést. Miután ezt megtettük, ismét futtassuk le a setup:upgrade magento parancsot a terminálból. Ha mindent jól csináltunk, a modulhoz tartozó táblánk létrejön az adatbázisban.

8) Modulhoz tartozó modellek és collection létrehozása

Ahhoz, hogy adatokkal tudjuk feltölteni az elkészült táblát vagy adatokat tudjunk lekérdezni, el kell készítenünk a modul model, resource és collection file-okat. Az alap model file az app/code/Aion/Test/Model könyvtárban lévő Test.php-ban valósítjuk meg. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Model;

/**
 * Aion Test model
 *
 * @method \Aion\Test\Model\ResourceModel\Test _getResource()
 * @method \Aion\Test\Model\ResourceModel\Test getResource()
 * @method string getId()
 * @method string getName()
 * @method string getEmail()
 * @method setSortOrder()
 * @method int getSortOrder()
 */
class Test extends \Magento\Framework\Model\AbstractModel
{
    /**
     * Statuses
     */
    const STATUS_ENABLED = 1;
    const STATUS_DISABLED = 0;

    /**
     * Aion Test cache tag
     */
    const CACHE_TAG = 'aion_test';

    /**
     * @var string
     */
    protected $_cacheTag = 'aion_test';

    /**
     * Prefix of model events names
     *
     * @var string
     */
    protected $_eventPrefix = 'aion_test';

    /**
     * @return void
     */
    protected function _construct()
    {
        $this->_init('Aion\Test\Model\ResourceModel\Test');
    }

    /**
     * Get identities
     *
     * @return array
     */
    public function getIdentities()
    {
        return [self::CACHE_TAG . '_' . $this->getId(), self::CACHE_TAG . '_' . $this->getId()];
    }

    /**
     * Prepare item's statuses
     *
     * @return array
     */
    public function getAvailableStatuses()
    {
        return [self::STATUS_ENABLED => __('Enabled'), self::STATUS_DISABLED => __('Disabled')];
    }

}

A model file-ban definiáljuk a Magento 2.0 cache működéshez szükséges két cache tag-et. Fontos még event prefix-et is definiálni, hogy később observer-ek használata esetén tudjuk használni, mint event name. A legfontosabb a _construct() függvény, melyben meghatározzuk a modellhez tartozó resource modellt. A getAvailableStatuses() függvényt és két STATUS_* konstansokat későbbi használatra célszerű létrehozni. A getIdentities() függvény implementálása nem kötelező, de a cache kezelés megfelelő működéséhez célszerű.   Miután elkészült az alap modell fájlunk, hozzuk létre a hozzá tartozó resource modellt is. Az alap resource modell fájlt az app/code/Aion/Test/Model/ResourceModel könyvtárban lévő Test.php-ban valósítjuk meg. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Model\ResourceModel;

/**
 * Aion Test resource model
 */
class Test extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
    /**
     * Define main table
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_init('aion_test', 'test_id');
    }
          
}

A legfontosabb, amit implementálnunk kell, a protected _construct() függvény, amiben meghatározzuk, hogy a korábban létrehozott táblához melyik mező a primary key. Miután elkészültünk a resource modellel, készítsük el a collection-t is. Az alap collection osztály fájlt az app/code/Aion/Test/Model/ResourceModel/Test könyvtárban lévő Collection.php-ban valósítjuk meg. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Model\ResourceModel\Test;

/**
 * Aion Test collection
 */
class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection
{
    /**
     * @var string
     */
    protected $_idFieldName = 'test_id';

    /**
     * Store manager
     *
     * @var \Magento\Store\Model\StoreManagerInterface
     */
    protected $storeManager;

    /**
     * @param \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory
     * @param \Psr\Log\LoggerInterface $logger
     * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy
     * @param \Magento\Framework\Event\ManagerInterface $eventManager
     * @param \Magento\Store\Model\StoreManagerInterface $storeManager
     * @param \Magento\Framework\DB\Adapter\AdapterInterface|null $connection
     * @param \Magento\Framework\Model\ResourceModel\Db\AbstractDb|null $resource
     */
    public function __construct(
        \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory,
        \Psr\Log\LoggerInterface $logger,
        \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy,
        \Magento\Framework\Event\ManagerInterface $eventManager,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
        \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource = null
    ) {
        parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $connection, $resource);
        $this->storeManager = $storeManager;
    }

    /**
     * Define resource model
     *
     * @return void
     */
    protected function _construct()
    {
        $this->_init('Aion\Test\Model\Test', 'Aion\Test\Model\ResourceModel\Test');
    }
}

A collection osztályban a _construct() függvényt implementáljuk, ahol valójában meghatározzuk, hogy a korábban elkészített modell osztályhoz melyik resource modell osztály tartozik. Lényegében ez elég is lenne collection-nek, azonban előretekintve helyezzük be (injektáljuk) már most a store manager osztályt is a public __construct() függvénybe későbbi felhasználás végett, hiszen minden modulnál követelmény a multistore támogatás. Amennyiben a három fájllal elkészültünk, akkor már létre is hoztuk azon osztályokat, melyekkel adatokat tudunk írni, ill. olvasni korábban létrehozott adatbázis táblánkból.

9) Tábla feltöltése adatokkal, script segítségével

A modell struktúra elkészítése szükséges volt ahhoz, hogy script-tel is tudjunk adatokat hozzáadni a modul alap táblájához. Az adat script-et az app/code/Aion/Test/Setup könyvtárban lévő InstallData.php-ban valósítjuk meg. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Setup;

use Aion\Test\Model\Test;
use Aion\Test\Model\TestFactory;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;

/**
 * @codeCoverageIgnore
 */
class InstallData implements InstallDataInterface
{
    /**
     * Test factory
     *
     * @var TestFactory
     */
    private $testFactory;

    /**
     * Init
     *
     * @param TestFactory $testFactory
     */
    public function __construct(TestFactory $testFactory)
    {
        $this->testFactory = $testFactory;
    }

    /**
     * {@inheritdoc}
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
     */
    public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        $testItems = [
            [
                'name' => 'John Doe',
                'email' => 'john.doe@example.com',
                'is_active' => 1,
            ],
            [
                'name' => 'Jane Doe',
                'email' => 'jane.doe@example.com',
                'is_active' => 0,
            ],

            [
                'name' => 'Steve Test',
                'email' => 'steve.test@example.com',
                'is_active' => 1,
            ],
        ];

        /**
         * Insert default items
         */
        foreach ($testItems as $data) {
            $this->createTest()->setData($data)->save();
        }

        $setup->endSetup();
    }

    /**
     * Create Test item
     *
     * @return Test
     */
    public function createTest()
    {
        return $this->testFactory->create();
    }
}

Az InstallData osztályban implementáljuk az install(…) függvényt, melyben létrehozzuk a teszt adatokat egy többdimenziós tömbben. Majd ezen végig iterálva meghívjuk a createTest() függvényt, melynek az új Test modell létrehozása a feladata, és a tömbök, mint adatok hozzáadása után mentjük a modellt. Itt fontos megemlíteni az osztályban létrehozott és __construct() függvényben injektált TestFactory osztályt is. Ezt az osztályt a Magento 2.0 hozza létre automatikusan a /var/generation/Aion/Test/Model könyvtárba az első futása során ($this->testFactory-> create()). Mivel még mindig a modulunk fejlesztése elején tartunk, dobjuk el manuálisan a korábban létrehozott „aion_test” táblát az adatbázisból, és töröljük a „setup_module” Magento 2.0 táblából a modulunkhoz tartozó bejegyzést. Miután ezt megtettük, ismét futtassuk le a setup:upgrade magento parancsot terminálból. Ha mindent jól csináltunk, a modulhoz tartozó táblánk ismét létrejön az adatbázisban immár adatokkal feltöltve. Természetesen ehelyett verziószám módosítással (emeléssel) is tudjuk az adatfeltöltést futtatni parancssorból, így ekkor nem kell manuálisan eldobni a korábban létrehozott táblánkat és „setup_module” táblába sem kell belenyúlni.

10) Tábla update script és verzió növelés

Ahogy fejlesztjük a modulunkat, sokszor kell módosítani az alap adatbázis táblát, esetleg új adatbázis táblát létrehozni. A Magento 1.x-ben ezt upgrade script-ekkel tehettük meg, mint adatbázis, mint pedig data oldalon. A Magento 2.0 modulunk sincs másképp, csak szerencsére az adatbázis script-eket egy fájlban kell ezúton kezelni, több különálló fájl helyett. Az adatbázis update script-et az app/code/Aion/Test/Setup könyvtárban lévő UpgradeSchema.php-ban valósítjuk meg. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */

namespace Aion\Test\Setup;

use Magento\Framework\Setup\UpgradeSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;

/**
 * @codeCoverageIgnore
 */
class UpgradeSchema implements UpgradeSchemaInterface
{
    /**
     * Upgrades DB schema, add sort_order
     *
     * @param SchemaSetupInterface $setup
     * @param ModuleContextInterface $context
     * @return void
     */
    public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
        if (version_compare($context->getVersion(), '2.0.1') < 0) {
            $setup->startSetup();
            $setup->getConnection()->addColumn(
                $setup->getTable('aion_test'),
                'sort_order',
                [
                    'type' => \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT,
                    'length' => null,
                    'nullable' => false,
                    'default' => 0,
                    'comment' => 'Test Sort Order'
                ]
            );
            $setup->endSetup();
        }
    }
}

A példában egy új mezőt (sort_order) adunk az alap adatbázis táblához („aion_test”) upgrade script segítségével. Az upgrade függvényben implementált módosítás csak akkor fog lefutni, ha a modul verziószáma eléri a példában látható 2.0.1 értéket.

11) Adatok megjelenítése frontend-en

Ahhoz, hogy frontend-en a létrehozott adatokat meg tudjuk jeleníteni, módosítani kell a frontend template-et és a hozzá tartozó block osztályt is. Az block osztályt már korábban elkészítettük, most kiegészítjük. A block osztály az  app/code/Aion/Block/ könyvtárban lévő Test.php-ban valósítjuk meg. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Aion\Test\Block;

use Magento\Framework\View\Element\Template;

/**
 * Aion Test Page block
 */
class Test extends Template
{
    /**
     * @var \Aion\Test\Model\Test
     */
    protected $test;

    /**
     * Test factory
     *
     * @var \Aion\Test\Model\TestFactory
     */
    protected $testFactory;

    /**
     * @var \Aion\Test\Model\ResourceModel\Test\CollectionFactory
     */
    protected $itemCollectionFactory;

    /**
     * @var \Aion\Test\Model\ResourceModel\Test\Collection
     */
    protected $items;

    /**
     * Test constructor.
     *
     * @param \Magento\Framework\View\Element\Template\Context $context
     * @param \Aion\Test\Model\Test $test
     * @param \Aion\Test\Model\TestFactory $testFactory
     * @param array $data
     */
    public function __construct(
        Template\Context $context,
        \Aion\Test\Model\Test $test,
        \Aion\Test\Model\TestFactory $testFactory,
        \Aion\Test\Model\ResourceModel\Test\CollectionFactory $itemCollectionFactory,
        array $data = []
    ) {
        $this->test = $test;
        $this->testFactory = $testFactory;
        $this->itemCollectionFactory = $itemCollectionFactory;
        parent::__construct($context, $data);
    }

    /**
     * Retrieve Test instance
     *
     * @return \Aion\Test\Model\Test
     */
    public function getTestModel()
    {
        if (!$this->hasData('test')) {
            if ($this->getTestId()) {
                /** @var \Aion\Test\Model\Test $test */
                $test = $this->testFactory->create();
                $test->load($this->getTestId());
            } else {
                $test = $this->test;
            }
            $this->setData('test', $test);
        }
        return $this->getData('test');
    }

    /**
     * Get items
     *
     * @return bool|\Aion\Test\Model\ResourceModel\Test\Collection
     */
    public function getItems()
    {
        if (!$this->items) {
            $this->items = $this->itemCollectionFactory->create()->addFieldToSelect(
                '*'
            )->addFieldToFilter(
                'is_active',
                ['eq' => \Aion\Test\Model\Test::STATUS_ENABLED]
            )->setOrder(
                'creation_time',
                'desc'
            );
        }
        return $this->items;
    }

    /**
     * Get Test Id
     *
     * @return int
     */
    public function getTestId()
    {
        return 1;
    }

    /**
     * Return identifiers for produced content
     *
     * @return array
     */
    public function getIdentities()
    {
        return [\Aion\Test\Model\Test::CACHE_TAG . '_' . $this->getTestModel()->getId()];
    }
} 

A block osztály konstruktorába implementáljuk a korábban létrehozott Test modelt és a hozzá tartozó testFactory-t és ezek mellett a collectionFactory-t (itemCollectionFactory) is. A collectionFactory osztály nagyon hasonló a testFactory-hoz, szintén a Magento 2.0 hozza létre a var/generation/Aion/Test/Model/ResourceModel/Test/ könyvtárban az első meghívása után. A getTestModel() függvény egy modellt példányosít, és a betöltésért felelős, míg a getItems() függvény egy teljes collection-t hoz létre.   A block-hoz tartozó template file-ban kiírathatjuk az adatokat tesztelésképp. A fájl tartalma:

<?php
/**
 * Copyright © 2016 AionNext Ltd. All rights reserved.
 * See COPYING.txt for license details.
 */
?>

<?php
/**
 * @var $block \Aion\Test\Block\Test
 */
?>
<div class="aion-test">
    <h2><?php echo __('This is a test extension!') ?></h2>
    <!-- See a sample model -->
    <?php \Zend_Debug::dump($block->getTestModel()->getData()); ?>
    <!-- See a sample collection -->
    <?php \Zend_Debug::dump($block->getItems()->getData()); ?>

    <!-- See a sample collection iteration -->
    <?php $items = $block->getItems(); ?>
    <?php if ($items->getSize()) : ?>
        <?php foreach ($items as $item) : ?>
            <h3><?php echo $block->stripTags($item->getName()) ?></h3>
            <p>
                <span><?php echo __('Email:'); ?></span>&nbsp;
                <span><?php echo $item->getEmail() ?></span>
            </p>
        <?php endforeach; ?>
    <?php endif; ?>
</div>

Ebben a cikkben részletesen átvettünk jó néhány témakört az alap Magento 2.0 modulunk fejlesztéséhez: hozzáadtunk egy saját adatbázis táblát, és teszt adatokkal feltöltöttük. Az ehhez szükséges modellt, resource-t és collection-t is létrehoztuk, ill. a teszt adatokat megjelenítettük a frontend-en is.   VÉGE AZ 1. RÉSZNEK   A 2. részben megismerkedünk a modulhoz tartozó admin táblázat (grid) elkészítésével, illetve a szükséges controllerek-kel.