Utvikler cookbook

4.15. Datamodell i EasyPublish-modul

Bakgrunn

Selv om de fleste nettsider kan bygges med standardobjekter i EasyPublish kan det av og til være nødvendig å jobbe med og vise data som ikke nødvendigvis passer inn i EPs standard datamodell. I disse tilfellene er det mulig å lage en skreddersømsmodul med en egen datamodell slik at både brukere og utviklere kan jobbe med disse dataene på en hensiktsmessig måte.

I denne artikkelen vil du lære:

Artikkelen bygger videre på det du lærte i "Hello world". Les gjennom denne om du ikke allerede har gjort det. Artikkelen tar kun stilling til hvordan man lager en datamodell og visning av data. Hvordan man lager et brukergrensesnitt for redigering av datamodellen vil bli beskrevet i en senere artikkel.

Fremgangsmåte

Du trenger

Caset

Dette eksempelet vil gå gjennom opprettelsen av en "bilbliotekmodul" som skal behandle en liste med bøker og vise disse på nettsiden. Modulen vil håndtere alt fra datalagring, behandling av data for presentasjon og til slutt visning på nettsiden. Vi vil bygge videre på dette eksempelet i senere artikler.

Modulen

Før vi begynner trenger vi skallet av en enkel skreddersømsmodul. Denne tar utgangspunkt HelloWorld-modulen fra forrige artikkel og skal ligge i filen lib/components/modules/Library/Library.php under mappen som tilhører domenet vårt.

<?php 

require_once SYSTEM_ROOT . '/lib/acf/component.php';

class Library extends Component {
        var $__FILE__ = __FILE__;
        var $__CLASS__ = __CLASS__;

        var $autoForwardActions = true;
        var $autoForwardViews = true;

        function __construct($constraints = null) {
                parent::__construct($constraints);

                $this->componentName = __CLASS__;

                $this->loadActionMappings();
                $this->loadDataAccessObjects();

                $this->actionObjectDefault = 'frontend';
                $this->requestMode = 'libraryMode';

                $this->authorized = $this->authorize();
        }

        function authorize($action = null, $modificator = null) {
                if($action == 'admin') {
                        return false;
                }

                return true;
        }
}

 

De fleste moduler vil ha et standardoppsett lik det over. Det er viktig å passe på at klassen har samme navn som filen den ligger i og mappen den ligger under (her "Library"). Modulen inneholder ellers en del boiler-plate som vil bli relevant senere, men enn så lenge lar vi dette ligge.

Definere databasetabell

Det første vi må foreta oss er å definere en databasetabell for modulen vår. Til biblioteket trenger vi en tabell for bøker (library_book) med felter for id og navn. Denne kan vi selvsagt opprette for hånd om vi vil, men for å kunne dokumentere strukturen og å gjøre det mulig å installere modulen gjennom EP bør vi definere et XML-schema for tabellen istedet. Denne XML-filen oppretter vi som lib/components/modules/Library/config/db.xml.

<?xml version="1.0"?>
<tables>
        <table name="library_book">
                <columns>
                        <column name="id" type="int(11)" auto_increment="1" not_null="1" />
                        <column name="name" type="varchar(256)" not_null="1" />
                </columns>
                <indices>
                        <index name="PRIMARY" type="BTREE">
                                <columns>
                                        <column name="id" />
                                </columns>
                        </index>
                </indices>
        </table>
</tables>

Navnet på databasetabeller som brukes av skreddersømsmoduler bør prefikses slik at de ikke skaper konflikter med EPs egne databasetabeller. Her har vi valgt navnet på modulen (library_*). Merk at vi også har definert en primærnøkkel på id-feltet.

Registrere modulen

For å få EP til å opprette databasetabellen vår i henhold til XML-schema må vi først fortelle EP om den nye modulen vår. Dette gjøres ved å registrere den i registeret.

 

Under Mine Moduler -> Register -> LOCAL -> SYSTEM -> Modules oppretter vi en ny mappe med samme navn som modulen ("Library"). I denne legger vi så strengverdier for displayname, domain og path som i bildet over. Viktig å merke seg her er at domain settes til "local.components". Dette forteller EP at modulen ligger i domenets "lib/components" og ikke i EPs egen "lib/components". Path beskriver hvor modulen ligger relativt til components-mappen.

En modul som er registrert i registeret på denne måten vil dukke opp under "Mine Moduler" i admin. Dette er i utgangspunktet kun interessant hvis modulen har et admingrensesnitt, noe bilbliotekmodulen ikke har (enda). Derfor bør vi fjerne modulen fra modullisten slik at brukerene vår ikke blir forvirret. Dette styres fra modulens authorize funksjon og er allerede håndtert i koden for Library modulen vi beskrev over.

Når EP bygger listen over moduler i admin vil det gå gjennom alle moduler registrert i registeret og instansiere modul-komponenten for hver av dem. Så kalles authorize('admin') og hvis dette returnerer 'true' bygges en node for modulen under "Mine Moduler". I en fullverdig skreddersømsmodul kan man i authorize styre tilgang til modulen basert på innlogget brukers tilganger, men i dette tilfellet nekter vi ganske enkelt admin-tilgang for å fjerne modulen fra "Mine Moduler" for alle brukere. Det er likevel viktig at vi kun nekter tilgang til 'admin' action slik at vi fortsatt kan bruke modulen til å vise data i frontend-objekter (mer om dette senere).

Installere modulen

Nå som EP vet hvor modulen ligger på filsystemet kan vi installere den og dermed få opprettet databasetabellen vår i henhold til schema. Dette gjøres under Mine Moduler -> ServerAdmin -> <domene> -> Moduler. 

 

Modulen vår dukker her opp med navnet vi fylte inn under "displayname" i registeret og hvis database-schema er ute av sync med db.xml i modulens config-mappe vil den også markeres som "ikke installert". For installere modulen og kjøre en database-sync fra schema er det bare å merke modulen og trykke "Installer" fra verktølinjen. Dette vil ta oss gjennom en wizard som bl.a. synkroniserer EPs database med modulens schema.

Når denne prosessen er fullført skal den databasetabellen vår være på plass i databasen og modulen markert som "installert".

Objektmodell

Rene databasetabeller og spørringer er ofte nok abstraksjon for helt trivielle prosjekter, men data følges gjerne av forretningslogikk som man ikke vil at skal lekke ut i alle deler av systemet. Derfor bør man først som sist isolere systemet fra dataene og operasjoner som jobber med disse. I en EP-modul stiller man relativt fritt til hvordan man vil gjøre dette, men dette eksempelet vil beskrive en potensiell måte å bygge en slik abstraksjon på.

Objektmodellen vår trenger støtte to typer operasjoner. De som opererer på hele eller deler av datasettet (tabelloperasjoner) og de som opererer på en spesifikk instans i datasettet (radoperasjoner). I EP er det vanlig å skille dette i metoder i to forskjellige klasser. En Manager som representerer tabellen og et DataObject som representerer en rad (bok) i tabellen.

Book DataObject

Vi starter med klassen som representerer en spesifikk bok i tabellen. Denne defineres i lib/components/modules/library/businessObjects/Book.php og arver av EPs DataObject-klasse.

<?php

use \Escio\EP\Util\DataObject;

class Book extends DataObject {
        protected static $properties = array(
                'id' => null,
                'name' => null
        );

        private $db;

        public function __construct($db, $values = null) {
                parent::__construct($values);

                $this->db = $db;
        }

        public function commit() {
                if($this->id) {
                        $query = '
                                UPDATE 
                                        library_book
                                SET 
                                        name = ?,
                                WHERE 
                                        id = ?
                        ';

                        $result = $this->db->Execute($query, array(
                                $this->name,
                                $this->id
                        ));

                        if(!$result) {
                                throw new Exception('Database error while updating book row.');
                        }
                } else {
                        $query = '
                                INSERT INTO library_book (
                                        name
                                ) VALUES (
                                        ?
                                )
                        ';

                        $result = $this->db->Execute($query, array(
                                $this->name
                        ));

                        if(!$result) {
                                throw new Exception('Database error whilte inserting new book row.');
                        }

                        $this->id = $this->db->Insert_ID();
                }
        }
}

 

DataObject gir oss en del nyttig funksjonalitet som vil bli viktig senere, men enn så lenge bruker vi den kun til å definere en PHP-representasjon av en rad i library_book-tabellen vår. I $properties arrayet defineres alle egenskaper vi vil gjøre tilgjengelig på instanser av Book. Array key er egenskapens navn, og verdien er standardverdi hvis ingen verdi blir eksplisitt satt. Her har vi brukt samme navn som feltene har i databasen.

Objektets constructor tar inn en referanse til ADODB-tilkoblingen til databasen tabellen vår ligger i, samt et valgfritt array med initielle property-verdier på samme form som $properties arrayet.

Commit-funksjonen bruker ADODB-tilkoblingen til å lagre bokinstansen til databasen vår. Om boken allerede har en ID (og dermed finnes i databasen) vil raden oppdateres. Om ikke, vil en ny rad opprettes og den nye radens ID lagres i objektets id-egenskap. Legg merke til at ved lagring hentes feltverdiene som om de var definert direkte som public egenskaper i Book-klassen. DataObject gjør alle egenskaper definert i $properties tilgjengelig med denne syntaxen. Det er dermed ikke nødvendig å skrive get/settere for disse egenskapene.

Med denne klassen på plass kan vi opprette nye bøker og legge dem i databasen vår.

<?php

$db = CTRL::db('local');

$book = new Book($db);
$book->name = 'Hitchhiker\'s Guide to the Galaxy';
$book->commit();

$book = new Book($db, array(
        'name' => '1984'
));
$book->commit();

 

Merk at vi her henter en referanse til lokal database via et kall til CTRL singleton og sender denne videre til objektet. CTRL er tilgjengelig fra hvor som helst i EP og kunne i teorien blitt hentet ut direkte i objektets commit-funksjon, men for å kommunisere at det finnes en kobling fra Book til databasen til andre utviklere, eller oss selv om 4 måneder, er det lurt å eksplisitt sende databasen inn som et parameter.

BookManager

Book-klassen gjør det mulig å opprette og endre på spesifikke bøker, men om vi f.eks. ønsker lage en liste over bøkene på siden vår trenger vi også å kunne hente dem ut igjen og iterere over dem. Dette er en typisk tabelloperasjon, og hører følgelig hjemme i klassen BookManager som vi legger i lib/components/modules/library/businessObjects/BookManager.php.

<?php

require_once dirname(__FILE__).'/Book.php';

class BookManager {
        private $db;

        public function __construct($db) {
                $this->db = $db;
        }

        public function getAll($limit = -1, $offset = -1) {
                $query = '
                        SELECT 
                                *
                        FROM
                                library_book
                ';

                $result = $this->db->SelectLimit($query, $limit, $offset);

                if(!$result) {
                        throw new Exception('Database error while fetching book rows.');
                }

                $books = array();

                foreach($result as $row) {
                        $books[] = new Book($this->db, $row);
                }

                return $books;
        }
}

 

I dette eksempelet har manager-klassen kun en metode som henter ut alle bok-radene fra databasen, oppretter Book-objekter som representerer disse og returnerer dem som et array.

Action og view

Nå som vi har fått datamodellen på plass er det på tide å få dataene ut i visning på nettsiden vår. I dette eksempelet skal vi bygge en enkel ul/li som viser alle bøkene som ligger lagret i databasen vår. Som vi lærte i forrige artikkel gjøres frontend-visning vha. en ACF-action og view som vi peker et EP_ACF_Compoent frontend-objekt mot. For å gjøre det klart hvilken funksjon disse er tiltenkt kaller vi dem "frontend" og legger dem i modulens actions og view mapper.

Først ut er action-klassen, som vi legger i  lib/components/modules/Library/actions/frontend.php.

<?php

require dirname(__FILE__).'/../businessObjects/BookManager.php';

class Library_Action_frontend extends EP_Action {
        public function bookList() {
                $db = CTRL::db('local');

                $manager = new BookManager($db);
                $books = $manager->getAll();

                $this->setParam('books', $books);
        }
}

 

Action-metoden vår har vi kalt bookList og den instansierer ganske enkelt BookManager og kaller getAll for å hente ut alle bøkene fra databasen. I action skal man ikke behandle data for visning. Som en controller i et tradisjonelt MVC-oppsett skal action kun hente inn parametre, behandle data og returnere disse til viewet for visning. Her sender vi data videre til viewet ved å sette disse i komponentens parameter 'books'. Parametre som settes på komponenten i action vil senere være tilgjengelig i viewet.

Neste kodesnutt er view-klassen som vi legger i lib/components/modules/Library/views/frontend.php.

<?php

class Library_View_frontend extends EP_View {
        public function bookList() {

                $books = $this->getParam('books');
                $data = array();

                foreach($books as $book) {
                        $data[] = array(
                                'id' => (int)$book->id,
                                'name' => htmlspecialchars($book->name, ENT_COMPAT, 'ISO-8859-1'),
                        );
                }

                $this->setOutput($data);
        }
}

View-funksjonen med navn bookList kjøres så snart bookList-action har kjørt og har ansvaret for å omforme de rå kildedataene til et format som kan benyttes for visning. I vårt tilfelle må skal vi vise bøkene i en HTML-liste så vi må iterere over bøkene og passe på at ingen spesialtegn kommer med videre til objektmalen.

Siste steg i viewet er å ta det behandlede bokarrayet vårt og bruke dette som viewets output. Dette gjør arrayet tilgjengelig for objektmalen vi skal bruke til visning.

Visning i frontend-objekt

Første steg i å opprette visningen er å lage en objektmal som bygger en ul/li av bøkene vi returnerte fra viewet. Denne oppretter vi under Mine Sider -> <domene> -> Maler.

 

Viktig å merke seg med malen er at bruksområde settes til "Objekt" og at $data arrayet vi sendte ut fra viewet via setOutput nå er tilgjengelig i $arr_item-variabelen i Smarty. Når malen er opprettet kan det også være en god ide og kategorisere den for bruk på EP_ACF_Component objekter. Dette gjøres ved å merke malen i listevisningen og trykke "Kategoriser" i verktøylinjen. Dette gjør malen lettere å finne igjen senere.

Siste steg er å opprette et nytt EP_ACF_Component objekt på en side på nettstedet vårt og peke dette mot frontend.bookList i modulen vår.

 

Objektet settes opp med objektmalen vi opprettet, plasseres i et passende plasseringspunkt og pekes til frontend.bookList i modulen vår. Påse at domene settes til local.components og ikke lib.components slik som standardvalg. På samme måte som når vi registrerte modulen vår i registeret betyr lib.components EPs egen components mappe, mens local.components refererer til domenets egen mappe.

En siste ting vi må gjøre før det er klart for å se om boklisten vår fungerer er å gå til avansert-fanen og påse at "Retur-modus" er satt til "Returnere data til Smarty".

 

Dette forteller EP at $data arrayet vi sendte med til setOutput i viewet skal sendes til objektmalen i $arr_item-variablen. Om vi istedet setter denne til "Skrive ut data til skjerm" vil EP ignorere objektmalen og skrive $data direkte til siden i objektets posisjon. Dette betyr at vi i teorien kan generere HTML og skrive ut denne direkte i viewet ved å sende en tekststreng til setOutput, men som oftest er det enklere å bruke en objektmal til dette.

Så snart vi har lagret objektet og navigert til siden vår skal forhåpentligvis noe liknende komme til syne:

 

Kanskje litt antiklimaktisk etter innsatsen, men dette er grunnsteinene som gjør det mulig å bygge hva som helst i EasyPublish. I senere artikler vil vi bygge videre på dette enkle eksempelet for å demonstrere admin-grensesnitt, tilgangskontroll og andre avanserte temaer.

27.02.15

Hjelp / support

Jeg finner ikke det jeg leter etter

Vår dokumentasjon er stadig under utvikling. Vi endrer og legger til nye kapitler og bøker etter hvert som EasyPublish CMS endrer og vokser. Skulle du likevel ikke finne det du leter etter så ta kontakt med oss via support@escio.no.

Jeg har funnet en feil

Hvis du har funnet en feil i vår dokumentasjon så ønsker vi å rette på den. Send oss et hint til support@escio.no.

Translate

You can translate this documentation by using Google Translate. Select your language:

×