NAS

PHP OOP: Attribute=Datenbankspalten

te one

Lt. Commander
Registriert
Apr. 2009
Beiträge
1.255
Hallo,

ich möchte in einer Klasse als Attribute alle Datenbankspalten hinterlegt haben.
Damit das ganze erweiterbar bleibt würde ich das gerne dynamsich lösen.
Hier mein Ansatz:

PHP:
<?php
    class User {
        private static $users=array();

        $result=db_query('SHOW COLUMNS FROM users');
        while ($row=  mysql_fetch_assoc($result)) {
            private $$row['Field'];
        }

        /* früher:
        private $id;
        private $mail;
        private $password;
        private $activated;
        private $activationKey;
        private $registrationDate;
        private $lastLogin;*/
?>
Ein Test zeigte, dass ich hiermit tatsächlich Variablen wie zB $id erzeugen kann.
Allerdings bringe ich das in der Klasse nicht hin...
Ich kann ja an dieser Stelle nicht einfach wie normal programmieren. Wie umgehe ich das?

Danke schonmal
 
Meiner Meinung nach kann das so nicht gehen...
Du könntest aber im Konstruktor der Klasse alle Spalten in ein assoziatives Array einlesen lassen...
 
@waYan:
Ich möchte aber noch keine Werte speichern sondern nur erstmal des Attribute anlegen.

@1668mib: Das mit dem Konstruktor ist so eine Sache....
Hier mal die Klasse wie sie bis jetzt aussieht:
PHP:
<?php
/*Example:
 *echo User::getById(1)->getMail();
 */
    class User {
        private static $users=array();
        
        private $id; //Sollte aus Tabellenspaltennamen gebildet werden
        private $mail; //Sollte auch aus Tabellenspaltennamen gebildet werden
        //usw

        //Zugriff auf User immer über diese Funktion
        public static function getById($id) {
            //Wenn Benutzer noch nicht als Objekt anliegt...
            if (!isset(self::$users[$id])) {
                //...erstellen
                self::$users[$id]=new User($id);
            }
            //Pointer zum Objekt zurückgeben
            return (self::$users[$id]);
        }

        public function __construct($id) {
            //Daten aus DB in Attribute schreiben
            $result=db_query('SELECT * FROM users WHERE id='.$id);
            $row=mysql_fetch_assoc($result);
            foreach ($row as $key => $value) {
                //Jetzt müsste zB $this->id=1 kommen
                $this->$row[$key]=$value;
            }
        }
        
        public function getMail() {
            return $this->mail;
        }
    }
?>
Werde wohl erstmal oben die Spalten fest hinterlegen, aber das müsste sich doch auch dynamisch irgendwie lösen lassen. (Ist ja gerade noch in der Entwicklung und dann kommen da ja immermal neue Felder dazu)
 
Zuletzt bearbeitet:
Lässt es sich doch... über ein assoziatives Array mit "Spaltenname"->"Wert".
Zugriff erhälst du dann z.B. über magische get- und set-Methoden.
 
Code:
<?php

class DbTable
{
	private $Data;
	
	public function __construct(Parameters)
	{
		$this->Data = array();
	}
	
	public function __set( $Property, $Value ) { $this->Data[$Property] = $Value; }
	public function __get( $Property ) { return $this->Data[$Property] }
	public function __isset( $Property ) { return isset( $this->Data[$Property] ); }
	public function __unset( $Property ) { if( isset( $this->Data[$Property] ) ) unset( $this->Data[$Property] ); }
}
Und ja, du kannst auch Instanzvariablen zur Laufzeit erzeugen.
 
@Daaron: stimmt...
Bis jetzt habe ich immer nur Methoden wie setMail gesehen.
Spricht etwas gegen die Nutzung von set("Mail","123@456.de")?
Bestimmt Behandlungen (zb Prüfung auf Gültigkeit der Mailadresse) könnte ich ja in einem Switch-Block machen.

edit: Gut, Yuuri hat das im Beispiel auch so. Dann werde ich das wohl so umbauen
 
Allgemein dürfte es für die meisten Attribute, die ja dynamisch erzeugt werden, gar keinen konkreten Aufruf von getDynamicAttribute irgendwo geben können, sondern eher in der Art wie bereits geschrieben: getAttribute("dynamicName")
Wichtige Attribute wie ID, Username, E-Mail wären natürlich dennoch in Form von direkten gettern ebenfalls realisiert...

Alternativ geht es allerdings auch so:

Es gibt in PHP ja eine Methode die aufgerufen wird, wenn keine "passende" Methode getroffen wurde: __call($funcname, $args)

so du könntest diese grob so implementieren:
PHP:
class User {
        private static $users=array();
	      //usw
 
        //Zugriff auf User immer über diese Funktion
        public static function getById($id) {
            //Wenn Benutzer noch nicht als Objekt anliegt...
            if (!isset(self::$users[$id])) {
                //...erstellen
                self::$users[$id]=new User($id);
            }
            //Pointer zum Objekt zurückgeben
            return (self::$users[$id]);
        }
 
        public function __construct($id) {
            //Daten aus DB in Attribute schreiben
            $result=db_query('SELECT * FROM users WHERE id='.$id);
            $row=mysql_fetch_assoc($result);
            foreach ($row as $key => $value) {
                //Jetzt müsste zB $this->id=1 kommen
                $this->$data[$key]=$value;
            }
        }
        
        public function __call($funcname, $args) {
            if (substr_compare($funcname, "get", 0, 3) == 0) {
            	//  getter
	            $field = strtolower(substr($funcname, 3));
	            return $this->$data[$field];
            }
            
            // setter ähnlich möglich...
        }
    }

Edit: Ich seh, es gibt ja sogar __set und __get dafür :-)


Edit: Wegen Gültigkeitsprüfungen:
Ich würde eher versuchen dafür spezielle Funktionen zu verwenden.. also für jedes Spalte in der Tabelle kann es einen solchen Validierer geben dann. Und der generische set-Aufruf schaut nur ob es eine solche Funktion gibt, ruft sie auf, und wenn es passt wird der Wert übernommen, ansonsten z.B. eine Exception geworfen...
Sonderbehandlungen für konkrete Dinge sollten meiner Meinung nach in generischem Code vermieden werden, stattdessen selbst durch generischen Code ausgedrückt werden (Generisch hier nicht im Sinn von Templates/Generischer Klassen gemeint).
 
Zuletzt bearbeitet:
Okay, da bastel ich mir was.

Jetzt aber nochmal zwecks Data-Array:
PHP:
    class User {
        private static $users=array(); //Beinhaltet Pointer zu den schon gecachten Usern
        private $data=array(); //Beinhaltet Daten eines Users
//...
        public function __construct($id) {
            //Daten aus DB in Attribute schreiben
            $result=db_query('SELECT * FROM users WHERE id='.$id);
            $row=mysql_fetch_assoc($result);
            
            foreach ($row as $key => $value) { //Zeile 26
                $this->data[$key]=$value;
            }
        }
Warning: Invalid argument supplied for foreach() in ... on line 26

Liegts am foreach oder macht das $this->data[$key]=$value; Probleme? Wie schreibe ich die Werte richtig ins Array, wenn das Array schon in der Klasse deklariert wurde? (oder passts?)

Die Daten würde ich dann zB mit $this->data['mail']; im Getter rausholen
 
Zuletzt bearbeitet:
Jap. Nutz ich persönlich aber nicht, da Code Completion so nur schlecht funktioniert. :)
 
Code Completion schön und gut, an anderen Stellen is es dafür echt nett für die Lesbarkeit. Mit entsprechenden Switch-Blöcken kannst du innerhalb der Klasse mit etwas kryptischen Variablennamen (z.B. strName, weils eben ein String ist) arbeiten, extern aber den Namen eines Users trotzdem via
PHP:
$user->Name = "Hans Wurst"
manipulieren.
 
Wie gesagt, wenn alle Spalten bekannt wären vorher, dann bräuchten wir den Umweg ja nicht gehen über die magischen Methoden... mir persönlich ist generischer Code eh lieber.


@TE: Da scheint dann $row kein passendes assoziatives Array zu sein... Edit: ok hat sich ja zeitgleich erledigt...

Edit:
Ich hoffe du nutzst zur User/Passwort-Prüfung dann nicht die User-Klasse, sonst wird da ständig die halbe User-Datenbank eingelesen und gecached... geht ja auch eleganter (effizienter) mit passenden SQL-Befehlen...
 
Zuletzt bearbeitet:
Naja, übers Klassenlayout und Variablenbenennung will ich jetzt eigentlich nicht philosophieren, aber meine Variablennamen beschreiben das, was enthalten ist, ohne Datentyp oder sonstigen Suffixen und Präfixen.

Bei Name ist mir bspw. klar, dass es ein string ist, da ein Name wohl kaum aus einer Zahl oder einem Datum besteht. Auch brauch ich kein iUserId oder sowas in der Klasse, das steht für mich im Namen.
 
@Yuuri: Stimme ich dir zu...
finde es oft lustig wenn ich im Code sowas seh wie "userList" oder so... ich nenn das Ding dann halt "users" usw... Typen Prä- und Suffixe machen für mich nur in Ausnahmefällen Sinn.
 
Ne ID könnte durchaus auch ein String sein. Denk doch nur mal an ne ISBN...
Egal... hübsch ist es nicht, aber magische Methoden haben ihre Vorteile... Ich nutz sie selten, aber z.B. der Code von Contao ist stellenweise rappelvoll damit.
 
1668mib schrieb:
Edit:
Ich hoffe du nutzst zur User/Passwort-Prüfung dann nicht die User-Klasse, sonst wird da ständig die halbe User-Datenbank eingelesen und gecached... geht ja auch eleganter (effizienter) mit passenden SQL-Befehlen...

Das mit dem Caching ist Absicht.
Wenn sich ein User einloggt, bauche ich mir natürlich etwas um das zu lösen ohne, dass alle User gecached werden.
Eine funktion getByMail($mail) wäre hier zB sinnvoll. Dann kann ich ihn anhand seiner mailadresse in den Cache schreiben :) (oder ich such halt die ID direkt über die DB raus und nehme dann getById...
Aber keine Angst, ich Cache nicht die komplette User-DB ;)
 
Yap schon klar mit dem Cache.

Ist es möglich, den Konstruktor privat zu machen?
Und im Grunde reicht zur Passwort-Prüfung - je nach Realisierung - auch eine statische Methode "gib mir den passenden User zu Login/Passwort"...
 
Hm, probiere ich später mal aus ob ein privater Konstruktor möglich ist. Denn eigentlich wird der wirklich nur intern aufgerufen...

Jup, mal sehen wie ich das dann letztendlich löse. Sind erst noch ein paar andere Dinge zu realisieren, bis das kommt :)
 
Zurück
Oben