OXID4ALL – Es muss ja nicht immer ein Shop sein … – Teil 3
9. Mai 2010 von urban |
Nun denn – auf zum Endspurt – und damit zum Kern des Projektes “oxid4project – Ein Projekterfassungs-Tool auf Basis von OXID CE”.
In diesem dritten und letzten Teil meines kleinen Workshops werde ich die Implementierung eigener Datenbanktabellen und damit verknüpfter core-Objekte demonstrieren. Dies bildet die Grundlage für die weitere Anwendungsentwicklung.
Am Ende des Beitrages steht dann der komplette Quellcode zum Download zur Verfügung.
Bisher hatten wir lediglich eine Login-Seite und eine noch ziemlich leere Startseite für das Projekterfassungs-Tool erstellt. Bevor wir das Ganze nun mit nützlicher Funktionalität füllen, wollen wir uns zunächst an die notwendigen core-Objekte machen.
Wie schon in Teil 1 beschrieben, benötigen wir folgende Objekte:
- Mitarbeiter / User: hier nutzen wir OXID Standardfunktionalität
- Projekte: dafür wird eine eigene core-Klasse benötigt
- Zeit- und Texterfassung: auch hierfür mache eine eigene core-Klasse Sinn
- Auswertungen: ebenfalls eine eigene core-Klasse
Um den Bereich “Mitarbeiter” kümmern wir uns im Moment überhaupt nicht – das erledigen wir komplett mit OXID-Standards. Für die Projektstammdaten sieht das anders aus:
Neues Objekt: Projekt(e) – Datenfelder
Um die Projektstammdaten zu erfassen, benötigen wir zunächst eine eigene Tabelle:
CREATE TABLE `azprojects` (
`OXID` varchar(32) NOT NULL,
`AZTITLE` varchar(150) NOT NULL,
`AZPRICEPERHOUR` double NOT NULL,
PRIMARY KEY (`OXID`)
);
Wir beschränken uns hier auf eine eindeutige ID, einen Titel und den Stundensatz. Um nicht mit OXID-eigenen Konventionen in Konflikt zu geraten, sollten eigene Tabellen und Tabellenfelder grundsätzlich ein eigenes Prefix bekommen – ich nutze stets “AZ” dafür. Eine Ausnahme bildet in unserer neu angelegten Tabelle allerdings das Feld OXID.
Im OXID Framework (OF) hat die sog. OX-ID eine besondere Stellung. Diese ID ist in allen Tabellen der primary key und somit immer die eindeutige ID für jeden Datensatz. Anhand dieser ID laden die OF-Basisklassen Inhalte in die Objekte hinein. Damit wir diesen sehr komfortablen Mechanismus nutzen können, müssen wir unserer Tabelle ebenfalls eine OX-ID geben.
Nun brauchen wir noch die zugehörige core-Klasse, die als Minimalversion so aussieht:
class azproject extends oxI18n
{
protected $_sCoreTbl = 'azprojects';
protected $_sClassName = 'azprojcect';
public function __construct()
{
parent::__construct();
$this->init( 'azprojects' );
}
}
Erläuterung:
Wir deklarieren die Klasse azproject als Extension von oxI18n. Diese Klasse wiederum ist Extension der Klasse oxBase – und die wiederum ist Extension von oxSuperCfg. Von dieser Vererbungskette profitieren wir in mehrfacher Hinsicht:
- oxSuperCfg: stellt uns mit Session- und Config-Objektgettern das Fundament des OF zur Verfügung
- oxBase: enthält alle Kernfunktionen für Datenobjekte, inkl. der notwendigen Methoden, um Daten aus Tabellen in Objekteigenschaften zu überführen und umgekehrt
- oxI18n: ist die sog. “Internationalization” Klasse, in der diverse Mechanismen hinterlegt sind, die es recht einfach machen, die Datenobjekte auch mehrsprachig zu pflegen. (Wir werden das im aktuellen Projekt nicht nutzen, aber um das Projekt möglichst universell nutzen zu können, macht es Sinn, diese Option hier zumindest mit einzubinden.)
Dieser OF-Komfort sorgt dafür, dass unsere eigene core-Klasse ziemlich bescheiden bleiben kann. Im Grunde definieren wir dort nur den Namen unseres Objektes ($_sClassName) und den Namen der Tabelle, aus der die Daten kommen ($_sCoreTbl). Das reicht bereits, um das grundlegende Handling unserer Projektstammdaten zu erledigen.
Wie das Ganze ins Template eingebunden wird, entnehmt ihr bitte dem Quellcode, der ja zum Download bereitliegt. Ich will hier nur kurz noch zeigen, wie nun das besagte Handling der Daten in die entsprechende View-Klasse azprojectsstart eingebunden wird.
Zuvor brauchen wir aber noch eine weitere core-Klasse – und zwar für ein Listenobjekt der Projektstammdaten. Warum das? – Nun, das OF enthält für nahezu alle Daten(core-)objekte jeweils eine Klasse für das Einzelobjekt und eine Klasse für das entsprechende Listenobjekt (z. B.: oxarticle / oxarticlelist, oxcategory / oxcategorylist, oxuser / oxuserlist etc.). Während die Klassen der Einzelobjekte wie oben beschrieben Extensions von oxBase oder oxI18n sind, sind die Listenklassen Extensions vonoxList. Die Klasse oxList implementiert einige vordefinierte Interfaces (ArrayAccess, Iterator, Countable) und bietet damit komfortable Möglichkeiten für den Umgang mit Objekten. Viel interessanter sind aber einige Standardmethoden dieser Klasse – wie z. B. selectString() – die einem das Leben extrem leicht machen können. Wir werden gleich sehen, was das konkret bedeutet. Zunächst aber legen wir unsere Listenklasse azprojectlist an wie folgt:
class azProjectList extends oxList
{
public function __construct( $sObjectsInListName = 'azproject')
{
return parent::__construct( 'azproject');
}
}
Ziemlich übersichtlich – nicht wahr?
Core-Objekt für Projektstammdaten in View-Klasse nutzen
Auf der Startseite für unser Tool soll die komplette Datenerfassung erfolgen. Hier sollen auch neue Projekte mit ihrem aktuellen Stundensatz angelegt werden können. Die vorhandenen Projekte sollen über ein Dropdown-Menü ausgewählt werden können:
Die Befüllung des Dropdown-Menüs erfolgt, indem wir in der View-Klasse azprojectsstart einen entsprechenden Getter implementieren:
public function getProjects()
{
$oProjectList = oxNew("azprojectlist");
$oProjectList->selectString("select oxid, aztitle from azprojects order by aztitle");
return $oProjectList->getArray();
}
Im Template sieht das dann folgendermaßen aus:
<select name="azdata[azjobs__azprojectid]">
<option value=""> -- Projekt auswählen -- </option>
[{foreach from=$oView->getProjects() item=oProject}]
<option value="[{$oProject->azprojects__oxid->value}]"
[{if $actProject == $oProject->azprojects__oxid->value}]selected[{/if}]>
[{$oProject->azprojects__aztitle->value}]
</option>
[{/foreach}]
</select>
Auch hier eine kurze Erläuterung:
Der Getter azprojectstart::getProjects() instanziiert ein neues Objekt der Listeklasse azprojectlist. Dies erfolgt mittels der (Factory-)Funktion oxNew(). Auch hier steckt wieder viel Komfort und Know How des OF’s drin: Die Funktion oxNew() sorgt z. B. dafür, dass – falls vorhanden – Module für die instanziierten Klassen berücksichtigt werden.
Auf dem Listenklassenobjekt können wir nun die Methode selectString() ausführen, die als Parameter ein simples Datenbankquery bekommt. Diese Methode sorgt nun dafür, dass das Listenobjekt $oProjectList als protected property ein Array enthält, welches die Einzelobjekte sämtlicher Datensätze enthält, die über das Query gefunden wurden. Über die (public) Methode getArray() auf dem Listenobjekt können wir diese Liste nun abrufen und darüber iterieren (siehe Template).
Neue Stammdaten via Core-Objekt in der Datenbank speichern
Nun fehlt noch das Speichern von neuen Projektstammdaten in der Datenbank. Um auch das ohne viel Aufwand erledigen zu können, kommt es darauf an, die Daten in einer speziellen Struktur zu erfassen, die der OF-eigenen Struktur bei Datenobjekten entspricht:
[Objektname]->[Tabellenname]__[Feldname]->value
Dementsprechend erhält unsere View-Klasse azprojectsstart nun folgende (protected) Methode:
protected function _createNewProject($title, $dPricePerHour)
{
$id = oxDb::getDb()->GetOne("select oxid from azprojects where aztitle = '$title'");
if(!empty($id)) {
$this->_aErrorMsg[] = "Projekt schon vorhanden!";
return "error";
}
$oProject = oxNew("azproject");
$oProject->azprojects__aztitle->value = $title;
$oProject->azprojects__azpriceperhour->value = $dPricePerHour;
$oProject->save();
return $oProject->getId();
}
Die Methode wird innerhalb der (public) Methode saveProjectData() aufgerufen und erhält als Parameter die Inhalte der beiden Formularfelder für Projekttitel und Stundensatz. Die genaue Struktur bitte im Quellcode nachvollziehen.
Zunächst überprüfen wir, ob evtl. schon ein Projekt mit demselben Namen existiert und übergeben ggf. eine entsprechende Fehlermeldung. Konnte das neue Projekt erfolgreich angelegt werden, so wird die (OX-)ID des neuen Datensatzes zurückgeliefert.
Die Erfassung der Projekt- bzw. “Job”-Daten
Wenn wir zuvor einige Beispielprojekte mit Stundensätzen angelegt haben, so können wir nun an die eigentliche Erfassung der Daten der einzelnen Jobs gehen. “Job” meint hier eine Arbeitseinheit eines Entwicklers an einem bestimmten Projekt.
Vom Grundprinzip her funktioniert das auf dieselbe Weise wie oben bei den Projektstammdaten beschrieben: Wir benötigen zwei core-Klassen azjob und azjoblist, dazu natürlich eine entsprechende Datenbanktabelle und noch die Einbindung in die View-Klasse azprojectsstart. Und da das Ganze hier ja ein Workshop sein soll (bzw. auf der Commons sein sollte …), kann das nun jeder für sich ausprobieren – oder aber sich im mitgelieferten Quellcode einfach anschauen, wie ich es gelöst habe.
Ein paar hilfreiche Hinweise zum Schluss
Da der gesamte Beitrag hier nun bereits deutlich länger geworden ist, als gedacht, möchte ich für den Rest der Anwendung auf den überlassenen Quellcode verweisen. Auf ein paar Dinge möchte ich aber noch hinweisen, weil sie ohne tiefergehende OXID-Kenntnis nicht sofort selbsterklärend sind:
functions.php
Innerhalb des modules-Verzeichnisses gibt es eine Datei functions.php, die standardmässig leer ist (abgesehen von einigen Kommentaren). Diese Datei ist dafür gedacht, eigene Funktionen anzulegen, die dann in allen übrigen PHP-Klassen genutzt werden können.
Kleiner Praxistipp: Es empfiehlt sich, die eigenen Funktionen in eine extra Datei auszulagern und in der Datei functions.php über includes einzubinden. Grund: Sollten einmal externe Module in die Anwendung eingebunden werden, die ebenfalls eigene Funktionen mitbringen, so müssen diese nicht jedes Mal in der functions.php zusammengeführt werden sondern es reicht dann das Hinzufügen einer weiteren include-Zeile.
Da es sich hier um Funktionen handelt und nicht um in Klassen gekapselte Methoden, sollten diese von vornherein eindeutig benannt werden, indem z. B. ein eindeutiges Prefix vorangestellt wird (in meinem Falle “az”).
Ich habe hier 2 Funktionen untergebracht, die für die Dropdowns Datum und Zeitaufwand benutzt werden, um hier sämtliche Daten der letzten 180 Tage sowie Zeitabschnitte in Viertelstundenschritten anzeigen zu lassen.
Für die restliche Funktionalität verzichte ich hier auf weitere Erläuterungen. Jeder kann sich diese anhand des mitglieferten Quellcodes selbst erschließen.
Abschließende Hinweise zu einer möglichen Weiterentwicklung des Projektes
Wer das Gesamtpaket einmal installiert und testet, wird schnell feststellen, dass hier noch etliche Wünsche offen bleiben. Speziell im Bereich der Auswertung könnte man sich noch viele nützliche Funktionen vorstellen. Gerade deshalb möchte ich das Projekt nun der Community zur Verfügung stellen, damit jeder, der Lust und Zeit hat, am Ausbau und an einer Verbesserung des Tools mitwirken kann. Wie schon gesagt werde ich versuchen, das Ganze schnellstmöglich als Projekt auf OXIDforge anzulegen, damit dann über diese Plattform eine gordnete Weiterentwicklung für Interessierte möglich wird.
Ein interessanter Aspekt der Weiterentwicklung könnte z. B. die Vereinfachung bei der Rechnungsstellung sein. Schließlich werkelt im Hintergrund ja ein Shop-Framework – es läge von daher nahe, aus abgerechneten Jobs Bestellungen zu generieren und hier dann entweder das Rechnungs-PDF des Shops zu nutzen oder aber die Daten über eine Schnittstelle in ein ERP-System zu überführen und dort dann automatisiert Rechnungen zu erstellen. Dafür müssten dann natürlich noch die Projektstammdaten mit Kundenstammdaten verknüpft werden, um einen vollständigen Bestelldatensatz generieren zu können. Das sollte sich aber durchaus machen lassen.
Jeder, der hier weiterführende Ideen beisteuern kann, ist herzlich eingeladen, sich an der Weiterentwicklung zu beteiligen! Sobald vorhanden, werde ich hier die entsprechenden Daten und Links bzgl. OXIDforge veröffentlichen.
Schlussbetrachtung im Kontext der zurückliegenden OXID Commons
Nachdem ich vorgestern den ersten Teil des Workshops hier veröffentlicht hatte, erhielt ich im Blick auf meine krankheitsbedingte Absage für die Commons kurz darauf einen Tweet von Roland Fesenmayr, den ich hier kurz zitieren möchte:
Wenn ich das lese, noch mehr schade, daß Du krank warst … hätte perfekt gepasst!
Tja, das habe ich auch mehrfach gedacht, als ich die Live-Streams der Commons aus dem Krankenbett heraus mitverfolgte. Da war ja mehrfach die Rede von einem “Ecommerce Betriebssystem” welches modular aufgebaut sein soll und bei dem sich der Shopbetreiber passend zu seinem individuellen Geschäftsmodell genau die Komponenten auswählen kann, die ihm hilfreich erscheinen.
Nun ist ein Projekterfassungs-Tool nicht gerade ein “Geschäftsmodell” im Rahmen des Ecommerce. Dennoch zeigt mein Beispiel, wozu das OXID-Framework prinzipiell bereits jetzt fähig ist. Wir haben hier eben kein total starres System, das ausschließlich auf fest definierte Ecommerce-Szenarien abgestellt ist – sondern wir haben hier bereits heute einen Baukasten vor uns, der – richtig genutzt – an so ziemlich jedes Geschäftsmodell angepasst werden kann.
Nicht erst seit der Commons ist klar, dass aktuell ein frischer Wind durch die Ecommerce-Landschaft bläst. Noch ist das eher ein laues Lüftchen, das aber – nicht zuletzt durch die Verbreitung des iPads – spürbar an Fahrt aufnimmt. Einkaufen im Internet ist nicht mehr bloß die Besorgung von Dingen, für die man früher in ein Ladengeschäft stiefelte – Einkaufen im Internet bekommt mehr und mehr eine eigenständige Erlebnisqualität. Während Onlineshops lange Zeit versuchten, das Einkaufen im Ladengeschäft möglichst originalgetreu nachzubilden, gibt es mehr und mehr Features, die dem Onlinekauf einen echten Mehrwert geben. Das sind so Dinge wie z. B. die Möglichkeit von Preisvergleichen, der Austausch in sozialen Netzwerken – das sind aber auch neue technische Möglichkeiten. Ich freue mich schon darauf, wenn ich zum ersten Mal ein Bild meines Wohnzimmers in mein (hoffentlich bald verfügbares) iPad einlesen kann und die Möbel aus dem Onlineshop mit den Fingern in meinem Wohnzimmer hin und herschieben kann.
Alles, was da auf uns zukommt, wird um so spannender werden, je flexibler die Softwarekomponenten sind, die zur Umsetzung der verschiedensten Ideen zur Verfügung stehen – und je offener die Schnittstellen dieser Komponenten untereinander sind. Möglicherweise läuft ein Onlineshop dann auf Basis von 10 verschiedenen Komponenten, die auf 10 verschiedenen Webservern (oder in 10 verschiedenen Clouds?) laufen.
Mit meinem Beispiel eines Projekterfassungs-Tools wollte ich demonstrieren, dass das OXID Framework m. E. schon jetzt die Flexibilität und Offenheit bietet, um darauf (fast) alles Mögliche zu bauen. Zumindest ist OXID hier auf einem guten Weg.
Hinweise zum Download
Der Download, der hier angeboten wird, ist ein Vorabangebot. Die Benutzung erfolgt komplett auf eigene Gefahr. Voraussetzung für die Nutzung der hier zur Verfügung gestellten Dateien ist eine frisch installierte OXID Community Edition 4.3.





Hi Andreas,
erst einmal danke für den Super-Workshop, sehr verständlich geschrieben! Ich hätte da aber noch einen Verbesserungsvorschlag: Die Programmierer würde ich als Hersteller im Shop verwalten. Sicher steht Dir dann erst einmal die Standard-Login-Funktionalität nicht mehr zur Verfügung. Der Vorteil wäre aber, dass Du den Shop dann direkt noch für Deine Kunden nutzen kannst. Du kannst dann schon fertig programmierte Module zum Kauf anbieten und das mit Arbeitsleistung und Projektmanagement etc. verknüpfen. Der Kunde könnte sich einloggen und im Kundenmenü die bereits geleisteten Arbeitsstunden sehen und auch welcher Mitarbeiter (Hersteller) am Projekt tätig ist. Das wäre auch für die Kunden sehr transparent – und vielleicht nimmt er beim Bearbeitungsstand checken noch ein kleines Modülchen zusätzlich in den Warenkorb.
Gruß
Alex
Hi Alex,
freut mich, dass es mir offenbar gelungen ist, das Ganze halbwegs verstädlich rüberzubringen. Vielen Dank für das Feedback!
Deine Idee bzgl. Programmierer und Hersteller finde ich sehr interessant! Auch die weiteren Perspektiven, die du aufzeigst. Das hat was!
Ich hoffe, dass ich das Ganze bald bei OXIDforge einstellen kann (im Moment gibt’s da noch irgendwie kleine technische Probleme) – und dann bin ich sehr gespannt, ob und wohin sich das Ganze weiterentwickelt.
Gruß!
Andreas
Hallo Andreas,
auch von mir ein Lob! Ein sehr sehr interessanter ansatz. Ich bin seit einiger Zeit an der Planung an solch einem Tool und muss sagen, dass dein Ansatz richtig gut ist. Mit einem “Abo-Modul” könnten wir dann auch gleich noch unser Hosting als Produkt anbieten und würden so keine Abrechnung mehr übersehen.
Vielen Dank für dieses super Tutorial! Eine Frage habe ich: Ist es denn im Sinne vom MVC-Pattern, wenn in einer View-Klasse ein SELECT-Statement auftaucht?
Hallo Ingo,
klare Antwort: Nein, ist es nicht. Und wenn man genauer hinsieht, dann präsentiert der OXID eShop auch so sein ganz eigene Interpretation des MVC’s. View- und Controller-Schicht verschwimmen hier ein wenig … – Und solange diesbezügliche Kritik nicht einfach nur strikt auf formalen Kriterien beruht, wird OXID und werde auch ich dafür immer ein offenes Ohr haben.
Wie man die SELECT-Statements aus den views heraushält, habe ich ja quasi im nächsten Blog-Beitrag – bzw. in dem Artikel im PHP-Magazin dann genauer beschrieben.
Gruß!
Andreas