Triggermethode in PHP
bobby
- php
Moin,
Hört sich jetzt komisch an. Ich hab ein ORM. Dies soll nun für I18N tauglich gemacht werden. Dabei habe ich einen Datensatz in einer Sprache und möchte nun einzelne Felder davon übersetzt anzeigen.
Dabei möchte ich mir aber keine Gedanken machen und irgendwelche Methoden zusätzlich aufrufen. Die ORM-Klassen sollen so schmal wie möglich sein.
Nun habe ich viel mit Reflection-Klassen herumprobiert und kann nun über Doc-Eigenschaften ganz gut das Lesen und Schreiben beeinflussen. Das funktioniert hervorragend. Nun möchte ich aber, dass wenn ich von einem ORM-Object eine Getter-Methode aufrufe, automatisch geprüft wird ob es sich bei der aufzurufenden Variable um eine zu übersetzende Variable handelt und dann natürlich den entsprechenden Wert ausliest.
Dazu habe ich der property ein Flag im DOC mitgegeben @I18N
Ich dachte nun an eine getriggerte Methode die automatisch beim Aufruf von ORM gettern prüft ob es sich um eine zu übersetzende Variable handelt.
Dabei hatte ich an magische Methoden gedacht. Leider greifen die nur, wenn eine Methode nicht aufrufbar ist.
Gibt es eine Möglichkeit eine Methode zu schreiben die IMMER getriggert wird? Egal welche andere Methode ich aufrufe? Ich hoffe das war soweit verständlich.
Gruß Bobby
Hallo bobby,
__get() is utilized for reading data from inaccessible properties. __call() is triggered when invoking inaccessible methods in an object context.
Mach die Properties bzw. die Getter-Methoden private.
Rolf
Moin,
Mach die Properties bzw. die Getter-Methoden private.
damit wird die Callmethode aufgerufen. OK. ABER ich hab dann ja gar keinen Zugriff mehr auf die Getter und die properties
Gruß Bobby
Hallo bobby,
ich habe es nicht ausprobiert. Die Idee war, dass die magic method IN der Klasse liegt und damit auf private Elemente zugreifen können sollte.
Du musst dann nur aufpassen, dass Du keine Rekursion erzeugst, denn wenn Du kein Property Foo hast und trotzdem $obj->Foo abrufen willst, würde die Magic Method sich vermutlich endlos selbst aufrufen, wenn Du blindlings den Call auf die vermeintliche private Foo-Methode delegieren willst. Das musst Du vorher mit method_exists oder property_exists sicherstellen.
Rolf
Moin,
Danke für den Tipp. Das werde ich beachten. Ich wollte halt die eigentlichen Entitätsklassen sehr schmal halten und Wiederkehrendes eben zentral behandeln.
Gruß Bobby
Tach!
Ich wollte halt die eigentlichen Entitätsklassen sehr schmal halten und Wiederkehrendes eben zentral behandeln.
Schlank (schmal wird es, wenn du im Editor den Umbruch auf 20 Zeichen stellst) sind deine Klassen aber nicht wirklich. Wenn du das schlank nur auf die "eigentlichen Entitätsklassen" beziehst, dann betreibst du sozusagen Eigenaugenwischerei, weil du den fetten Unterbau ausblendest. Wiederkehrendes kann man auch vermeiden, indem man die Zuständigkeiten trennt. Deine Entitätsklassen brauchen den Datenbankzugriff nur an bestimmten Stellen deines Programms. An anderen, beispielsweise bei der Ausgabe, braucht es diesen Unterbau nicht. Deswegen ist Active Record für das große Ganze nur bedingt nützlich und im Rest bestenfalls überflüssig.
Nehmen wir mal an, du möchtest in Zukunft deine Entitäten serialisieren, weil du sie als JSON an die SPA senden möchtest. Du musst dann berücksichtigen, was von der Klasse zum Active Record gehört und damit übergangen werden muss. Kein Problem, sagst du dir, bau ich das noch in die Basisklasse ein. Und dann kommt die nächste Anforderung, die auch wieder ... und irgendwann hast du eine eierlegende Wollmilchsau mit all ihren Nachteilen aufgrund ihrer Komplexität. Deswegen gibt es Programmiermodelle, die wirklich auf schlanke Bestandteile bedacht sind. Zum Beispiel immer, wenn in der Beschreibung, was ein Ding macht, das Wort "und" vorkommt, ist das ein guter Indikator um über die Trennung von Zuständigkeiten nachzudenken. Lösungen gehen zum Beispiel in die Richtung, dass Datenbankabfragen für jedes Entity in einem eigenen Repository abgehandelt werden, das seinerseits sich der Dienste der Datenbankabstraktion bedient. Meist reicht in typisierten Sprachen wie C#, dass man sich da ein generisches Repository mit der grundlegenden CRUD-Funktionalität baut, das man dann lediglich konkret typisiert instantiiert. Vorgänge der Geschäftslogik werden von Services erbracht. Das beinhaltet auch komplexe Datenverarbeitungsvorgänge über mehrere Entitäten hinweg, was man dann mit einem Unit of Work kapselt. Auch das Übersetzen wäre eine Tätigkeit für einen Service. Der kann auch gut und gerne seinerseits entsprechende Repositorys und andere Services befragen, um seine Aufgabe zu erledigen.
Leider hat diese Trennerei auch wieder einen Nachteil. Man möchte nämlich auch nicht, dass all diese Services ihrerseits wild um sich greifen, um das heranzuziehen, was sie zum Arbeiten brauchen. Es ist auch nicht besonders schön, wenn man all diese Abhängigkeiten zu Fuß auflösen muss, bevor man auch nur mit einem einzigen Service arbeiten kann. Und da ist dann der nächste Schritt hin zur Dependency Injection und einem DI-Framework, das diese Abhängigkeiten eigenständig auflösen kann.
Vielleicht wirst du diesen Weg nicht gehen wollen, weil das wohl einen kompletten Umbau deines Programms bedeuten würde, aber ich wollte es wenigstens mal erwähnt haben.
dedlfix.
Moin,
Das mag sein. Für meine Anwendungen reicht dies so. Die Active Records blende ich mit einem Flag aus.
Aber Danke für deine Ausführungen. Die werde ich natürlich in zukünftige Überlegungen einfließen lassen.
Gruß Bobby
Moin,
Hört sich jetzt komisch an. Ich hab ein ORM.
Ist das eine homegrown Library oder irgendetwas, das man kennen könnte?
Gibt es eine Möglichkeit eine Methode zu schreiben die IMMER getriggert wird? Egal welche andere Methode ich aufrufe? Ich hoffe das war soweit verständlich.
Nicht dass ich wüsste. Du könntest mit einem Decorator- oder Proxy-Designmuster an dein Ziel gelangen, aber das würde bedeuten, dass du nicht mehr direkt mit den ORM-Klassen arbeitest, sondern mit einer zusätzliche Indirektions-Schicht vorliebnehmen müsstest. Wenn du das ORM selber implementiert hast, kannst du die Getter-Methoden entsprechend anpassen und könntest deine öffentliche API so rückwärts-kompatibel halten. Ich weiß nicht, ob es in PHP soetwas wie Aspekte gibt, aber das könnte noch ein Stichwort für dich sein.
Moin,
Ist das eine homegrown Library oder irgendetwas, das man kennen könnte?
Nein. Eine eigene Lösung
Decorator- oder Proxy-Designmuster an dein Ziel gelangen, aber das würde bedeuten, dass du nicht mehr direkt mit den ORM-Klassen arbeitest, sondern mit einer zusätzliche Indirektions-Schicht vorliebnehmen müsstest
Könntest du das mal genauer beschreiben? Ich habe ja schon eine "Übersetzungsschicht" zwischen den ORM-Klassen und der Datenbankklasse
Gruß Bobby
Ist das eine homegrown Library oder irgendetwas, das man kennen könnte?
Nein. Eine eigene Lösung
Ist der Quelltext irgendwo einzusehen?
Könntest du das mal genauer beschreiben? Ich habe ja schon eine "Übersetzungsschicht" zwischen den ORM-Klassen und der Datenbankklasse
Schwierig ohne deinen Code zu kennen, aber ich versuch mal ein Beispiel zu geben, ich gehe davon aus, dass du eine Klasse Model
in deinem ORM hast. Ein Proxy für das Model, würde auf der Model-Klasse aufbauen, und sie um Mehrsprachigkeit erweitern.
class I18NProxy {
protected model;
public function __construct(Model $model) {
$this->model = $model;
}
public function __get(string $name) {
if ($this->model instanceof I18N && $this->model->canTranslate($name)) {
return $this->model->translate($name);
} else {
return $this->model->__get($name);
}
}
}
interface I18N {
public function canTranslate(string $name) : boolean;
public function translate(string $name);
}
Angenommen bisher hast du einen Aufruf $model->greeting
um eine englische Begrüßungsformel zu erhalten, müsstest du nun folgedes schreiben, um eine übersetzte Begrüßungsformel zu erhaten:
$greeting = (new I18NProxy($model))->greeting;
Moin,
Schwierig ohne deinen Code zu kennen
Sorry... der ist schon sehr umfangreich
aber evtl. hilf die kurze Erklärung unter dedlfix seinem Beitrag?
Die Struktur schein doch nicht ganz dafür geeignet. Das I18N-objekt ist ein globales Objekt. Das ich einfach nur mit den Parametern aufrufe. Die einzelnen Entitäten erben von einer ORM-Klasse die sich um die ganze Übersetzung DB<->Object kümmert. Das läuft teils über statische Methoden teils über geerbte Methoden.
Gruß Bobby
Moin,
Schwierig ohne deinen Code zu kennen
Sorry... der ist schon sehr umfangreich
Du, das glaub ich dir sofort, ein ORM ist eine anspruchsvolle Geschichte. Das macht es auch so schwierig eine konkrete Antwort auf deine Frage zu geben. Könntest du für uns vielleicht reduziertes Beispiel bauen, das uns ungefähr zeigt, wie dein ORM bisher funktioniert und wie du dir die API vorstellst, die das ganze um Mehrsprachigkeit erweitern soll? Interface-Deklarationen ohne konkrete Implementierung wäre da schon ein großer Schritt, um dir helfen zu können.
Moin,
OK... Ein Beispiel (gekürzt):
<?php
declare(strict_types=1);
namespace orm;
use DateTime;
use stdClass;
/**
* Class Content
* @package orm
* @table content
*/
class Content extends Orm {
/**
* ID
* @var integer
*/
private $id = 0;
/**
* Artikeltitel
* @var string
* @I18N
*/
private $title = '';
/**
* Konstruktor
* @param stdClass|null $content
* @throws \ReflectionException
*/
public function __construct( stdClass $content = null ){
parent::__construct( $content );
}
/**
* ID Setter
* @param int $id
*/
public function setId( int $id ) :void {
$this->id = $id;
}
/**
* Setter Titel
* @param string $title
*/
public function setTitle( string $title ) :void {
$this->title = $title;
}
/**
* getter Id
* @return int id
*/
public function getId():int {
return (int) $this->id;
}
/**
* Getter Title
* @return string Title
*/
public function getTitle():string {
return (string) $this->title;
}
}
das wäre eine einfache ORM-Entität. Das soll auch wirklich nicht mehr drin stehn. So dazu gibt es die Klasse ORM
<?php
declare( strict_types = 1);
namespace orm;
use inc\{
BasicDb
};
use PDOStatement;
/**
* ORM Class Main ORM Class
*/
class Orm extends BasicDb {
/**
* Pointer for internal list counting
* @var int
*/
private $point = -1;
/**
* Query->Builder Object
* @var string
*/
private $queryVars;
/**
* result array
* @var array
*/
private $list = array();
/**
* Construktor
* @param \stdClass|null $obj
* @throws \ReflectionException
*/
public function __construct( \stdClass $obj = null ) {
// call parent constructor
parent::__construct();
// initialize queryVars- QueryBuilder object
$this->queryVars = (object) array();
// if get a stdClass Object (from addToListFunction)
if($obj !== null ):
// an dieser Stelle werden die Eigenschaften über eine reflection ausgelesen und das Object entsprechend erzeugt
endif;
}
/**
* Methode to set a table
* @param string $table
*/
protected function setTable(string $table) {
// setzt tabelle (Code gerade unwichtig)
}
/**
* Methode to add criterias to query builder
* @param string $typ wich criteria type
* @param string|array|object|int|boolean $value Value
* @param string|array|object|int|boolean $arrvalue optional Arrayvalue
*/
public function addCriteria(string $typ, $value, $arrvalue = null) {
if($arrvalue === null)
$this->queryVars->$typ = $value;
else
$this->queryVars->$typ[$value] = $arrvalue;
}
/**
* Query start
* @param bool $debug
* @return array result array
*/
protected function startQuery(bool $debug=false):array{
if($debug!==false)
return parent::createQuery($this->queryVars,false,true);
return parent::createQuery($this->queryVars );
}
/**
* get actually pointer from internal list
* @return int
*/
public function getPointer() : int {
return $this->point;
}
/**
* add 1 to pointer
* set ponter to -1 if end of list
* @return int
*/
protected function setPointer():int{
$this->point = $this->point+1;
if($this->point >= sizeof($this->list)):
$this->point=-1;
endif;
return $this->point;
}
/**
* get result list
* @return array
*/
public function getList():array {
return $this->list;
}
/**
* add 1 object to list
* @param $obj
* @param \stdClass $data
*/
protected function addToList( $obj, \stdClass $data = null ) : void {
foreach($this->extFields AS $key => $value)
$this->setExtData($obj, $key, $value, $data);
$this->list[] = $obj;
}
/**
* @param bool $noduplicate
* @return int
* @throws \ReflectionException
*/
public function save( bool $noduplicate = false ) : int {
// methode zum Speichern der DAten. Wird wieder gesteuert über die Flags in der Entität
}
/**
* Methode to delete a Element
* @return number[]|string[]|PDOStatement
*/
protected function deleteElement(){
// löschmethode
}
/**
* Method to get all Entrys of an ORM-Class
* this method will calling from getById() and getBy() too
* @param int $anzahl
* @return OrmDb ORM-Object with result List
* @throws \ReflectionException
*/
public static function getAll($anzahl = null) : OrmDb {
// Name from the calling Class
$objectname = static::class;
// new orm object for result return
$ormobj = new OrmDb();
// set table name
if(empty($ormobj->queryVars->table))
$ormobj->setTable(self::getTable($objectname));
// do request
if( $anzahl !== null )
$ormobj->addCriteria( 'cps', $anzahl );
foreach( (array) $ormobj->startQuery() as $obj )
$ormobj->addToList( new $objectname( $obj ),$obj );
// return ORM object wirth result list
return $ormobj;
}
/**
* Method to get the next Entry of List
* return next entry
* @return mixed
*/
public function getEntry() {
return $this->list[ $this->setPointer() ];
}
/**
* Method to get Table from PHPDoc or from static var (old system)
* @param string $objectname
* @return mixed
* @throws \ReflectionException
*/
public static function getTable( string $objectname ) {
// new Reflection Class to read PHPDoc
$r = new \ReflectionClass(static::class );
// get Class Comment
$doc = $r->getDocComment();
// seperater the Tbale declaration
preg_match('~@table ([^ \n\r]*)~',(string) $doc, $erg);
return $erg[1];
}
}
Das erstmal die grobe Struktur.
Aufgerufen wie folgt:
$contentfactory = \orm\Content::getAll();
$content = $contentfactory->getEntry();
echo $content->getTitle();
Und genau am letzten Punkt soll eben abgefangen werden dass die EIgenschaft "Title" ein Flag @I18N hat und dann entsprechend übersetzt wird. Dazu habe ich jetzt den Getter für Titel auuf private gesetzt und eine __call()-methode eingefügt. Diese liefert auch die Übersetzung. ich möchte aber, wenn keine Übersetzung vorhanden ist, den originaltext aus $title anzeigen lassen. Da ich aber $titel und den getter auf private stehen habe, erlange ich keinen Zugriff darauf.
(Der Code ist übrigens stark gekürzt, aber ich denke das relevante ist dabei)
Gruß Bobby
Moin,
return $this->model->__get($name);
auch das hatte ich schon in betracht gezogen. Es funktioniert aber leider nicht.
Ich hab die Methode mal auf private gesetzt und dann folgendes in der Haupt-ORM-Klasse notiert
public function __call($name, $arguments) {
if( preg_match('~get([^ ]*)~', $name, $field) ) :
// hier jetzt die prüfung auf Übersetzung
// Lass ich jetzt mal weg. das Funktioniert
endif;
$f = lcfirst($field[1]);
return $this->$f;
}
public function __get( $name ){
return $this->$name
}
ich hatte eigentlich so verstanden dass mit der __get-methode der Zugriff auf gesperrte Daten ermöglicht wird. Leider ist dies nicht der Fall. Über var_dump($this) bekomm ich aber das richtige Object angezeigt.
Gruß Bobby
Hallo bobby,
bei dem Design deiner Modell-Klassen sollte das aber funktionieren. Gerade mal im Sandkasten gespielt, man kann den Hauptteil sogar hineinvererben:
class OrmBaseClass {
public function __call($name, $args) {
if (substr($name,0,3) == "get") {
$prop = lcfirst(substr($name,3));
if (property_exists($this, $prop))
return $this->ormGetProperty($prop);
else
echo "Property '$prop' not found\n";
}
if ($name == "ormGetProperty")
echo "Du Depp, deine Modellklasse braucht eine ormGetProperty Methode!\n";
}
}
class Modell extends OrmBaseClass {
protected function ormGetProperty($name) { return $this->$name; }
private $otto = 17;
}
$z = new Modell();
var_dump($z);
echo "Otto ist " . $z->getOtto() . "\n";
echo "Hugo ist " . $z->getHugo() . "\n";
Der ormGetProperty Spion in der Modellklasse ist nötig, weil die Basisklasse ja nicht auf private Properties zugreifen kann.
Allerdings - wenn man erstmal SO WEIT ist, dann kann man die Werte der Properties auch in einem namensindexierten Array in OrmBaseClass speichern und braucht die Modellklassen nur noch als Namenscontainer, oder vielleicht zum Hinzufügen von etwas Logik.
Rolf
Moin,
Naja... das Design ist doch etwas anders. In dem gezeigten Fall wäre ja das Model die Entitätsklasse. Oder?
ich habe <Enttitätsklasse> -> erbt von -> <ORM-Klasse> -> erbt von -> <DB-Klasse>
Da gibts kein richtiges Model.
Und eben das wollt ich nicht, dass ich in jede Entität was wiederholendes rein schreibe.
Wie gesagt, mit protected funktionierts nun wie gewünscht. Die Methoden protected können auch von der "BaseORM" aufgerufen werden. Ich prüfe nun allerdings ob die aufzurufende Methode existiert, damit keine Rekursion entsteht. Der Tipp war echt gut.
Danke dafür
Gruß Bobby
Hallo bobby,
dass eine Superklasse auf protected-Elemente einer Subklasse zugreifen kann, ist eine PHP Spezialität. Sozusagen eine automatische Erzeugung virtueller Methoden.
Nach mehreren geistigen Iterationen bin ich aber bei der Idee angekommen, dass das alles eigentlich nur unnötiger Boilerplate-Code ist. Weil:
Und dann fragt man sich: Warum überhaupt noch Entity-Klassen. Warum nicht…
class ORM {
// Typkatalog, kommt aus irgendeiner Config-Quelle. Typprüfung könnte man noch einbauen.
// Katalog könnte auch über Klassenmodell repräsentiert werden.
private static $types = [
"Person" => [
"table" => "PersTable",
"properties" => ["name" => "string", "plz" => "string", "ort" => "string"]
]
];
private $type;
private $properties;
private $values = [];
public function __construct($type) {
// Unbekannte Typen abweisen
if (!isset(ORM::$types[$type])) throw new Exception();
// Property-Definitionen für diesen Entity-Typ übernehmen. $this->properties könnte man
// auch weglassen, dann muss man nur in jedem get/set durch den den Katalog navigieren
$this->type = $type;
$this->properties = ORM::$types[$type]["properties"];
}
// Zentrales Handling für get und set mit Speicherung der Werte in einem Array.
public function __call($name, $args) {
$operation = substr($name,0,3);
if ($operation != "get" && $operation != "set")
throw new Exception("Aufruf von unbekannter Methode $name");
$prop = lcfirst(substr($name,3));
if (!isset($this->properties[$prop]))
throw new Exception("$operation auf unbekanntes Property $prop");
if ($operation == "get") {
if (!isset($this->values[$prop])) return null;
$value = $this->values[$prop];
// Add I18N here
return $value;
} else {
if (count($args) != 1) throw new Exception("Setter erwartet genau einen Parameter");
// I18N here?
$this->values[$prop] = $args[0];
}
}
}
// Subklasse nur nötig weil eine Zusatzmethode gebaut wurde
class Person extends ORM {
public function __construct() {
parent::__construct("Person");
}
public function getAdresse() {
return $this->getPlz() ." ". $this->getOrt();
}
}
$e = new ORM("Person"); // Pseudo-POPO Entitäten
$e = new Person(); // Konstruktion von erweiterten Entitäten MIT eigenen Methoden
$e->setName("Rolf");
$e->setPlz("12345");
$e->setOrt("Dingenskirchen");
$name = $e->getName();
echo "Name ist $name\n";
echo "Adresse ist {$e->getAdresse()}\n";
Damit brauchst Du grundsätzlich nur noch noch Entitätsobjekte vom Typ ORM. Nur wenn Du noch Zusatzmethoden implementieren willst, wie hier für getAdresse, erzeugst Du eine abgeleitete Klasse. Natürlich kannst Du - der Einheitlichkeit halber - auch für jede Entität eine Subklasse ableiten, die nur den Konstruktor enthält.
Rolf
Moin,
Oh... das klingt interessant.
da könnte ich verfügbare felder auch direkt aus der Table auslesen mit entsprechenden Typ und somit nur quasi "on the fly" den Typkatalog erstellen lassen. Oder ich schreib die Klassen wie bisher und lass nur Getter und Setter weg, sowie definiere nur Sonderdinge als getter und Setter. Das eröffnet natürlich Möglichkeiten
Gruß Bobby
Tach!
Dabei hatte ich an magische Methoden gedacht. Leider greifen die nur, wenn eine Methode nicht aufrufbar ist.
Du kannst da wohl nur die Getter weglassen und stattdessen ihren Arbeit in der magischen Methode erledigen.
dedlfix.
Moin,
Du kannst da wohl nur die Getter weglassen und stattdessen ihren Arbeit in der magischen Methode erledigen.
Hm... die hat aber keinen Zugriff auf die privaten Variablen. Ich habe die ORM-Klassen und eine Zwischeninstanz zum Übersetzen in "Datenbanksprache"
Und genau diese Zwischeninstanz verarbeitet die Reflections der aufrufenden Class. Ich habe versucht mit __get() das ganze abzufangen. Leider weiterhin ohne Erfolg
Gruß Bobby
Tach!
Du kannst da wohl nur die Getter weglassen und stattdessen ihren Arbeit in der magischen Methode erledigen.
Hm... die hat aber keinen Zugriff auf die privaten Variablen.
Die ist doch eine Methode derselben Klasse und muss darauf Zugriff haben. Oder ich habe nicht verstanden, wie deine Struktur genau aussieht.
Ich denke, der bessere Weg wird wohl der von Rolf B sein. Ich hätte das wohl auch vorgeschlagen, wenn ich gewusst hätte, dass unzureichende Sichtbarkeit auch die Magie auslöst.
dedlfix.
Moin,
OK... kurzer Abriss
ich habe für jede Entity eine ORM-Klasse. z.B. Artikel mit vielen privaten Properties z.B. $title
So. Nun habe ich zu jedem Property einen Getter und Setter. Die geben ja nur stupide die Eigenschaften zurück.
Die ganze Factory-Funktionalität habe ich in einer Elternklasse "ORM" geschrieben, von der die einzelnen Entitätsklassen erben.
Diese Elternklasse sucht sich nun aus der aufrufenden Entitätsklasse alle Eigenschaften über eine Reflection heraus, holt die Daten aus der DB und erzeugt die Objekte und füllt diese VOLLKOMMEN automatisch die Objekte. DAs Speichern läuft ebenso voll automatisch. Gesteuert wird dies eben durch Flags in den Docs der properties
mal ein Beispiel
$artikel = \orm\Artikel::getById(1);
dabei wird mir der Artikel über die ORM-Klasse geholt und als vollständiges Object zurück gegeben.
$article->setContent('ABC');
$article->save();
Hier wird, wie üblich der Content gesetzt und ich speicher die Daten, was wieder über die ORM-Klasse funktioniert.
Nun möchte ich aber dass bei
$article->getContent();
Nicht zwingend der Englische Text ausgegeben wird, sondern evtl. eine vorhandene Übersetzung. ich hatte zuerst direkt beim Auslesen aus der DB die Daten ersetzt mit der jeweiligen Sprache. ABER wenn ich dann das Object wieder mit der save()-methode speichern würde, würde der falsche Text in die DB geschrieben (z.B. deutsch statt englisch)
Die Übersetzungen gehen übrigens alle über eine einzige Translate Tabelle.
ich bräuchte also einen Weg bei dem mir bei getContent() nicht automatisch der englische Text ausgegeben werden würde, sondern wenn vorhanden die Übersetzung. Und dies OHNE in dem Getter rumschreiben zu müssen. Die Entitätsklassen möchte ich doch eher allgemein halten und nur durch die Flags steuern.
Und ja, die Magie wird durch Ändern der Sichtbarkeit ausgelöst, was mir aber immer noch keinen Zugriff auf die Methode beschert.
Gruß Bobby
Tach!
ich habe für jede Entity eine ORM-Klasse. z.B. Artikel mit vielen privaten Properties z.B. $title
So. Nun habe ich zu jedem Property einen Getter und Setter. Die geben ja nur stupide die Eigenschaften zurück.
Schon allein das ist so langweilig niederzuschreiben, dass ich stattdessen Magie verwendet hätte. Wenn es wirklich eine eigene Art von Zugriff für ein Feld braucht, kann man das immer noch als separate Methode ausführen.
Noch besser als Methodenaufrufe zu notieren wäre das direkte Zugreifen auf die Eigenschaften. Höre ich da Kapselung? Was will man da groß kapseln, wenn es sowieso ein 1:1-Durchgriff ist. Bei Abweichungen lässt man die Eigenschaft weg und __get()/__set() greifen ins Geschehen ein, worüber man dann das Verhalten steuern kann. Vorteil ist auch, dass jede Menge sinnlose Methodenaufrufe wegfallen. Die sind ja auch nicht kostenlos zu haben.
$artikel = \orm\Artikel::getById(1);
Active-Record-Prinzip. Bin ich kein Fan von. Ich mag lieber POxOs und Repositories für das DB-Handling. Schon allein wegen der Trennung der Zuständigkeiten. Aber egal.
ich bräuchte also einen Weg bei dem mir bei getContent() nicht automatisch der englische Text ausgegeben werden würde, sondern wenn vorhanden die Übersetzung.
Eigentlich wäre das eine Aufgabe für einen Lokalisierer/Normalisierer, um die zusätzliche Funktionalität des Hin- und Herübersetzens aus der Datenbankgeschichte rauszuhalten. Am besten ist es, wenn solange wie möglich normalisert gearbeitet und erst zur Ausgabe übersetzt wird.
Und dies OHNE in dem Getter rumschreiben zu müssen. Die Entitätsklassen möchte ich doch eher allgemein halten und nur durch die Flags steuern.
Ich denken, mit 1unitedpowers Vorschlag wird die Geschichte am saubersten.
dedlfix.
Moin,
Höre ich da Kapselung? Was will man da groß kapseln, wenn es sowieso ein 1:1-Durchgriff ist.
na bei einigen Entitäten möchte man vielleicht getter oder setter kapseln. Ich finde dies schon wichtig. zumindest machen das viele ORM so (z.B. JPA).
Gruß Bobby
Tach!
Höre ich da Kapselung? Was will man da groß kapseln, wenn es sowieso ein 1:1-Durchgriff ist.
na bei einigen Entitäten möchte man vielleicht getter oder setter kapseln. Ich finde dies schon wichtig. zumindest machen das viele ORM so (z.B. JPA).
Bei einigen, richtig, aber nicht bei allen. Wir sind zwar hier bei PHP und müssen uns mit dessen Möglichkeiten abfinden, aber ich schweife mal kurz zu C# ab. Dort gibt es zwei Arten von Klassenvariablen: Fields und Propertys. Fields sind die einfachen Eigenschaften mit direktem Schreib- und Lesezugriff. Eine Property hingegen hat Getter und Setter eingebaut. Der Zugriff erfolgt wie bei Fields und anderen Variablen mit einfachem Lese- und Schreibzugriff. Früher musste man da noch selbst den Zugriff auf ein privates Field in den Getter/Setter reinschreiben.
private int id;
public int Id {
get { return id; }
set { id = value; }
}
Das war selbst mit Makro in der IDE unschön, weil man beim Lesen des Codes weiterhin aufpassen musste, ob da nicht doch was anderes stand als ein 1:1-Zugriff auf das Backing Field. Das hat man auch erkannt und Syntactic Sugar hinzugefügt, so dass man nur noch
public int Id { get; set; }
schreibt und fertig ist. Man hat sozusagen die sinnlose Kapselung syntaktisch entfernt. Intern findet sie weiterhin statt, damit man kompatibel bleibt für die Fälle, in denen man die Kapselung wirklich benötigt.
dedlfix.
Tach!
[..] Wir sind zwar hier bei PHP und müssen uns mit dessen Möglichkeiten abfinden, aber ich schweife mal kurz zu C# ab.
Ja schön! Im Perl würde man das mit tie()
erledigen. Da sind Getter und Setter als Interface~Methods FETCH() und STORE() bereits vordefiniert und können einfach überladen werden. D.h., diese Methoden werden automatisch aufgerufen wenn man auf die Eigenschaften einer Instanz zugreift. Und ebenda kann auch eine Übersetzungstabelle greifen.
MfG
Moin,
Die Lösung ist manchmal SOOOOOO einfach. Ich habe einfach den getter auf PROTECTED statt PRIVATE gesetzt. Somit hat die ORM-Klasse Zugriff darauf, von außen aber nicht und so muss die __Call-Methode durchlaufen werden. Diese kann aber den original-getter aber aufrufen, wenn keine Übersetzung vorhanden ist.
Somit danke an die vielen Tipps.
Gruß Bobby