Klasse während Laufzeit erstellen...

Darian

Erfahrenes Mitglied
Hallo Leute,

ich habe gerade etwas vor, und bin mir nun gar nicht so sicher ob das Möglich ist, darum wollte ich einmal ein paar Infos und Meinung einholen.

Zur Idee:

Ich möchte gerne einen Object Relation Mapper für meine zukünftigen Projekte Coden. Man soll damit einfach Abfragen und Inserts (und mehr) machen können.

Benutzt wird auch die aktuelle Version von AdoDB.

Anwendungsbeispiel:
PHP:
//Name der Tabelle und Primarykey => sollte automatisch selektieren.
$user= new User(1);

echo $user->name;
echo $user->password;

//neuer user erstellen
$new_user= new User();

//Werte setzen
$new_user->name = "Test";
$new_user->password = "ADRUTKCVKLA";

//in Datenbank eintragen
$new_user->insert();

Nun ist es so dass eine direkte Klasse user gar nicht gibt. Ich möchte dass er dieses bei Laufzeit erstellt. Sprich dass er Attribute setzt je nachdem welche Spalten es in der Datenbank gibt, und diese automatisch von einer anderen Klasse erbt.

Wenn es die Klasse bereits gibt, soll er sie einach includieren. (das ist aber eh einfach)

Ist es also vom allgemeinen her überhaupt möglich sowas zu machen? Eine Klasse so zu erstellen, oder werde ich dann in Zukunft immer für jede Tabelle von Hand eine Klasse schreiben müssen? (außerdem wären dann auch Daten zwei mal gespeichert => unnötige Redundanz) Darum wollte ich dass er das automatisch aus der DB ausliest.

Ich hoffe ich konnte mein eher komisches Problem verständlich erklären, und ich bekomme hilfreiche Antworten.

lg und danke für jede Antwort
Darian
 
Zuletzt bearbeitet:
Ich würde eine Benutzerverwaltungs-Klasse einsetzen, um die User-Klasseninstanzen zu laden und zu speichern, etwa:
PHP:
class User {
	private $props;

	public function __construct($id=null) {
		$this->props = array(
			'id' => null,
			'name' => null,
			'password' => null,
		);
		if (is_int($id)) {
			$this->props['id'] = $id;
		}
	}

	public function getProperty($name) {
		// TODO: prüfen, ob Lesen erlaubt ist (Passwort sollte beispielsweise nicht lesbar sein)
		if (isset($this->props[$name])) {
			return $this->props[$name];
		}
	}

	public function setProperty($name, $value) {
		// TODO: prüfen, ob Änderung erlaubt ist (ID sollte beispielsweise nicht änderbar sein)
		$this->props[$name] = $value;
	}
}


class UserAdministration {
	private $dbConnection;

	public function storeUser(User $user) {
		if ($this->userExists($user)) {
			$query = 'UPDATE …';
		} else {
			$query = 'INSERT INTO …';
		}
		return $this->dbConnection->execute($query);
	}

	public function &loadUser($id) {
		$query = 'SELECT … FROM `users` WHERE `id` = '.(int)$id;
		$row = $this->dbConnection->executeQuery($query)->fetchRow();
		$user = new User();
		foreach ($row as $key => $val) {
			$user->setProperty($key, $val);
		}
		return $user;
	}

	public function userExists(User $user) {
		if (!is_null($user->getId()) {
			return false;
		}
		$query = 'SELECT 1 FROM `users` WHERE `id` = '.$user->getId();
		return $this->dbConnection->executeQuery($query)->countRows();
	}
}
PHP:
// existierenden Benutzer holen
$user = $userAdministration->loadUser(1);

// neuen Benutzer speichern
$user = new User();
$user->setProperty('name', 'John Doe');
$user->setProperty('password', 'secret');
$userAdministration->storeUser($user);
 
Hallo Leute,

erst einmal danke für die Infos. Leider hat mir der Link von SCIPIO nicht so weiter geholfen, sie bereden zwar das selbe Thema, ich persönlich komme da auf keinen grünen Zweig.

@Gumbo, das mit der Userklasse ist auch klar, und werde darauf noch einmal zurück kommen müssen. Derzeit suche ich aber nach eine allgemeinen Lösung. Aber danke für die Infos, brauche ich nach dem Datanbankmanager. :)

Gibt es sonst noch irgendwelche Ideen diesbezüglich, bin schon langsam am verzweifeln.

Habe jetzt meine Klasse wie folgt gemacht:

PHP:
<?php

class user extends DatabaseManager
{
	public $UserID;
	public $UserName;
	public $Password;
	public $FirstName;
	public $LastName;
	public $Street;
	public $HouseNumber;
	public $LocationID;
	public $Birthday;	
}

class user_group extends DatabaseManager
{
	public $UserID;
	public $GroupID;	
}
?>

Im Grunde habe ich da nur die Datenbank Spalten kopiert, und darum wollte ich dass mein Programm es irgendwie automatisch auslest, und somit die Klasse auch automatisch zur Laufzeit erstellt wird.

Bin mir aber nicht sicher ob sowas überhaupt möglich ist.

Jedenfalls noch einmal danke für weitere Infos.

lg Darian
 
Hier mal nen beispiel...
PHP:
<?php
class ActiveRecord{
    protected $__columns = array();
    protected $__table = false;
    public function __set($key, $value){
        if(!in_array($key, array("__columns", "__table"))){
            $this->$key = $value;
        }
    }
    public function __get($key){
        if(isset($this->$key)){
            return $this->$key;
        }
        return false;
    }
    public function __isset($key){
        return isset($this->$key);
    }
    public function __unset($key){
        if(isset($this->$key)){
            unset($this->$key);
        }
    }
    protected function afterSave(){}
    protected function afterUpdate(){}
    protected function afterInsert(){}
    protected function afterDelete(){}
    protected function beforeSave(){}
    protected function beforeUpdate(){}
    protected function beforeInsert(){}
    protected function beforeDelete(){}
    public function save(){
        $this->beforeSave();
        if(isset($this->id)){
            $this->beforeUpdate();
            $this->update();
            $this->afterUpdate();
        }else{
            $this->beforeInsert();
            $this->insert();
            $this->afterInsert();
        }
        $this->afterSave();
        return $this;
    }
    public function update(){
        $table = $this->getTableName();
        if($table !== false){
            $conn = $this->getDbConnect();
            $updatefields = array();
            $columns = $this->getColumns();
            foreach($this as $key => $value){
                if(in_array($key, $columns)){
                    $value = $conn->escape($value);
                    $updatefields[] = "`".$key."` = '".$value."'";
                }
            }

            $query = "update `%s` set %s where `%s`.`id` = %d";
            $query = sprintf(
                        $query,
                        $table,
                        implode(", ", (array)$updatefields),
                        $table,
                        (int)$this->id
                    );

            $conn->query($query);
            return $this;
        }
        return false;
    }
    public function insert(){
        $table = $this->getTableName();
        if($table !== false){
            $conn = $this->getDbConnect();
            $fields = array();
            $values = array();
            $columns = $this->getColumns();
            foreach($this as $key => $value){
                if(in_array($key, $columns)){
                    $fields[] = "`".$key."`";
                    $values[] = "'".$conn->escape($value)."'";
                }
            }

            $query = "insert into `%s` (%s) values (%s)";
            $query = sprintf(
                        $query,
                        $table,
                        implode(", ", (array)$fields),
                        implode(", ", (array)$values)
                    );

            $conn->query($query);
            $this->id = $conn->insertid();
            return $this;
        }
        return false;
    }
    public function delete($params = array()){
        ...
    }
    final protected function getTableName(){
        if($this->__table !== false){
            return $this->__table;
        }
        return false;
    }
    final protected function getColumns(){
        if(empty($this->__columns)){
            $query = "show columns from `".$this->__table."`";
            $result = $this->getDbConnect()->fetchAssocAll($query);
            foreach($result as $row){
                $this->__columns[] = $row["Field"];
            }
        }
        return $this->__columns;
    }
}
?>

<?php
class User extend ActiveRecord{
    protected $__table = "user";
// select auswertungen...
protected function beforeInsert(){
$this->create = date("Y-m-d H:i:s", time());
}
}
?>
Pflicht ist halt die Varialbe $__table;
Performant ist das sicher nicht, wenn du jedesmal erst auswerten muß was für felder vorhanden sind...

PHP:
$rookie = new User();
$rookie->name = "Rookie";
$rookie->password = "md5...";
$rookie->save();
Womöglich hilft dir dies ja...
 
Jetzt habe ich das wichtigste vergessen...
Basteln noch funktionen wie getClass, setClass, setTable
dann könntest / kannst du auch dynamisch klassen oder zumindest speichern...

PHP:
$user = new ActiveRecord();
$user->setTable("User");
$user->... = ...;
$user->save();

(schön ist jedoch sicher nicht...)
mit getColumns ist in der Entwicklunszeit wo Performance noch nicht relevant von Vorteil...
 
Hey Rooki, danke für die Infos, werde ich gleich einmal probieren.

Wenn es wirklich so ein performant Problem ist, muss ich das noch einmal genau testen.

Was mir aber aufgefallen ist, folgendes funktioniert auch ohne dass ich ein Attribut, oder die Methoden __set() oder __get() definiert habe.

PHP:
$user = new user(3);
$user->test = "egal";
echo $user->test;

Komisch dass es auch ohne diese Methoden geht

Ursprünglich war ja mein Plan dass ich gar keine Klasse user schreiben muss, aber ganz davon weg kommen wird man wohl nicht, und ich denke ich werde nrianoch einbauen dass man Pflichtfelder angeben kann, und das wird dann auch vorher noch überprüft.

lg Darian
 
notfalls gedrungen gibt es noch eine andere option.
PHP:
$user = new stdClass();
$user->name = "test";
$adapter = new DatabaseAdapter();
$adapter->save($user);
jedoch benötigst du dafür eine fixe bezeichnung für tabelle ansonsten hätte der adapter keine ahnung auf welche tabelle das ganze bezogen ist...
 
Hi,

das Performance-Problem mit "getColumns()" lässt sich recht einfach in den Griff kriegen, indem man das Ergebnis in einem Cache speichert, da sich die Definition der Tabelle ja nicht ständig ändert.

Das könnte z.B. so funktionieren (Dateien als Cache):
PHP:
// Verzeichnis, in dem die Cache Dateien gespeichert werden:
define('COLUMN_CACHE_DIR', 'cache/column');

// Gültigkeitsdauer des Cache in Sekunden:
define('COLUMN_CACHE_LIFETIME', 3600);

...

    final protected function getColumns(){
        if(empty($this->__columns)){

            $cacheFile = COLUMN_CACHE_DIR . '/' . $this->__table . '.dat';

            if(file_exists($cacheFile) && filemtime($cacheFile) > (time() - COLUMN_CACHE_LIFETIME))
            {
              $this->__columns = unserialize(file_get_contents($cacheFile));
            }
            else
            {
              $query = "show columns from `".$this->__table."`";
              $result = $this->getDbConnect()->fetchAssocAll($query);
              foreach($result as $row){
                  $this->__columns[] = $row["Field"];
              }

              file_put_contents($cacheFile, serialize($this->__columns));
            }
        }
        return $this->__columns;
    }
Das ist nur mal ein schneller Entwurf, natürlich kann man es auch anderes lösen. Die globalen Konstanten könnte man natürlich auch als Klassenkonstanten definieren. Einfach mal ein biserl rumprobieren, was am besten gefällt... ;)
 
Hallo Leute, und danke für die Infos, werde das probieren müssen.

Aber das mit dem Cachen ist sicher eine gute Idee, und ich werde dafür eine Methode in meine Klasse einbauen. Gefällt mir ganz gut.

Na ja, werde sobald mal mehr von der Klasse zu sehen ist, es hier einfach veröffentlichen, und um Meinungen fragen.

lg Darian
 

Neue Beiträge

Zurück