Active Record Pattern

  • Themenstarter Themenstarter Bgag
  • Beginndatum Beginndatum
B

Bgag

Hallo!
Ich habe mal wieder ein kleines Problem. Ich speichere in mehreren Tabellen daten, wie zum Beispiel Autoren, Links, Bücher oder ähnliches. All diese Datensätze werden ähnlich behandelt. Es sollen Daten
  • hinzugefügt
  • gelöscht
  • geändert
  • ausgelesen
werden. Für diese Aktionen habe ich bisher kleinere Klassen geschrieben, die diese Aufgaben für mich vereinfachten. Leider ist es sehr mühselig, für jede einzelne solcher Tabellen eine Klasse zu schreiben. Daher hatte ich gehofft eine Klasse schreiben zu können, die es mir erlaubt, so konfortabel wie möglich diese Aufgaben zu abstrahieren. Bisher ist mir das leider nicht gelungen.

Dann kam ich auf die Idee, dass es ja eigentlich ganz praktisch wäre, könnte ich ein Objekt erzeugen, dass als Spiegel einer Datenbank funktioniert. Füge ich also eine Zeile im Objekt hinzu, lösche oder ändere sie, so soll dies auch in der Datenbank geschehen. Die Ausgabe von Datensätzen wäre dann meiner Meinung nach ohnehin komfortabler. Leider ist mir bisher aber auch nicht gelungen soetwas zu implementieren.

Nun bin ich allerdings auf ein Design Pattern gestoßen, dass eine ähnliche Idee beschreibt. Das Active record Pattern. Leider verstehe ich dieses Pattern noch nicht vollends und kann es daher nicht implementieren. Kann mir vielleicht jemand dieses Pattern erläutern?
MfG, Andy
 
Welches Active record pattern benutzt du denn?

Eigentlich ist das ganz einfach, du hast nachdem du das Pattern benutzt ein Objekt mit welchen du deine Datenbank verwalten kannst. Das ganze passiert durch Methoden der Klasse.
Wie das mit dem jeweiligen Pattern funktioniert ist in der jeweiligen Dokumentation nachzulesen.
 
Du könntest das ganze so gestalten:
PHP:
$objArticle = new core_article();
$objArticle->setName("Artikel name");
$objArticle->setPrice(123);
$objArticle->save();

Es kommt ganz darauf an ob das Active Record äußerst dynamisch sein soll oder nicht.
Interessant dürfte für dich auch die __call Funktion sein.
 
Hallo!

Vielen Dank für deine schnelle Antwort. Ich weiß leider nicht, was du damit meinst, welches Active Record Pattern ich denn benutze. Ich kenne nur die von Martin Flower beschriebene Theorie. Eine sinnvolle Implementierung habe ich leider nicht gefunden und zudem würde ich das auch gern selbst machen, damit ich es verstehe.

Das ich mit diesem Objekt Daten in einer datenbank über die Methoden verwalten kann ist mir schon klar. Allerdings habe ich noch nicht so recht verstanden, ob man nun immer nur mit einer Zeile arbeitet, oder ob man Zugriff auf die ganze Tabelle hat. Vielleicht kannst du ja auch diese Frage klären.

Von welcher Dokumentation du da redest, weiß ich leider nicht. Freue mich aber über jeden Link.

MfG, Andy

//EDIT: Auch ein Danke an dich tobee. Ich möchte das Active record Pattern so dynamisch wie nur möglich implementieren. Von daher ist die __call()-Methode, wie auch andere magische Methoden von großer Bedeutung für mich. Mittels der __call()-Methode kann ich zum Beispiel das Setzen von Felder regeln.
 
Zuletzt bearbeitet von einem Moderator:
Hallo Andy,

Das ich mit diesem Objekt Daten in einer datenbank über die Methoden verwalten kann ist mir schon klar. Allerdings habe ich noch nicht so recht verstanden, ob man nun immer nur mit einer Zeile arbeitet, oder ob man Zugriff auf die ganze Tabelle hat. Vielleicht kannst du ja auch diese Frage klären.
Die Zuordnung sieht bei diesem Pattern folgendermaßen aus: Eine Datenbanktabelle/-sicht wird in der Applikation durch eine Klasse repräsentiert. Eine Instanz dieser Klasse korrespondiert dann zu einer Zeile der entsprechenden Tabelle/Sicht. Will man also eine ganz bestimmte Zeile auslesen/verändern, erstellt man sich dazu eine Instanz der Klasse. Will man stattdessen Informationen über die gesamte Tabelle einbringen (z.B. die Anzahl der Zeilen), verwendet man dafür Klassenmethoden.

Grüße,
Matthias
 
Vielen vielen Dank für deine Antwort, Matthias. Meinst du mit Klassenmethoden statische Methoden, die zum Beispiel die Anzahl der Zeilen ausgeben können? Wie könnte ich eine solche Klasse abstrahieren? Welche Methoden müsste ich implementieren? Brauche ein paar Anregungen, damit ich weiter verfahren kann.
MfG, Andy
 
Eine Klasse? Na gut das ist möglich - ohne Frage. Ich hab mir mal was ausgedacht und anbei findest du das Ergebnis:

content.class.php
PHP:
<?php
class content {
	
	const TYPE_BOOKS	= 1;
	const TYPE_AUTHORS	= 2;
	const TYPE_LINKS	= 3;
	
	private $nCurrentType = null;
	
	/**
	 * Erstellt das Objekt.
	 *
	 * @param numeric $nType
	 * @return unknown
	 */
	public function __construct($nType) {
				
		if(empty($nType) || !is_numeric($nType)) {
			return false;
		}
		
		$this->nCurrentType = $nType;
		
	}
	
	/**
	 * Fügt einen Datensatz hinzu.
	 */
	public function add() {
		
		$aArgs = func_get_args();
		
		switch ($this->nCurrentType) {
			
			case self::TYPE_BOOKS:
				var_dump(count($aArgs));
				if(count($aArgs) > 3 || count($aArgs) == 4) {
					$this->addBook($aArgs[0], $aArgs[1], $aArgs[2], $aArgs[3]);					
				}
				else {
					return false;
				}
				break;
				
			case self::TYPE_AUTHORS:
				$this->addAuthor();
				break;
			
			case self::TYPE_LINKS:
				$this->addLink();
				break;
				
		}
		
	}
	
	/**
	 * Editiert einen Datensatz.
	 */
	public function edit() {
		
		$aArgs   = func_get_args();
		$nItemId = array_shift($aArgs); 
		
		switch ($this->nCurrentType) {
			
			case self::TYPE_BOOKS:
				$this->editBook();
				break;
				
			case self::TYPE_AUTHORS:
				$this->editAuthor();
				break;
			
			case self::TYPE_LINKS:
				$this->editLink();
				break;
				
		}
		
	}
	
	/**
	 * Löscht einen Datensatz.
	 *
	 * @param numeric $nItemId
	 * @return bool
	 */
	public function delete($nItemId) {
		
		if(empty($nItemId) || !is_numeric($nItemId)) {
			return false;
		}
		
		switch ($this->nCurrentType) {
			
			case self::TYPE_BOOKS:
				$this->deleteBook();
				break;
				
			case self::TYPE_AUTHORS:
				$this->deleteAuthor();
				break;
			
			case self::TYPE_LINKS:
				$this->deleteLink();
				break;
				
		}
		
	}
	
	/**
	 * Fügt ein Buch hinzu.
	 *
	 */
	private function addBook($cTitle, $cDescription, $mCreated, $cWebsite) {
		
		if(empty($cTitle) || !is_string($cTitle)) {
			return false;
		}
		
		if(empty($cDescription) || !is_string($cDescription)) {
			return false;
		}

		if(empty($mCreated) || !is_string($mCreated)) {
			return false;
		}

		if(empty($cWebsite) || !is_string($cWebsite)) {
			return false;
		}
		
		$cSql = "
		INSERT INTO
			books
		SET
			title = '".database::mysql()->real_escape_string($cTitle)."',
			description = '".database::mysql()->real_escape_string($cDescription)."',
			created = '".database::mysql()->real_escape_string($mCreated)."',
			website = '".database::mysql()->real_escape_string($cWebsite)."',
			added = NOW()";
		
		database::mysql()->query($cSql);
		return true;
		
	}
	
	/**
	 * Fügt einen Autor hinzu.
	 *
	 */
	private function addAuthor() {
		
	}
	
	/**
	 * Fügt einen Link hinzu.
	 *
	 */
	private function addLink() {
		
	}
	
	/**
	 * Editiert ein Buch.
	 *
	 */
	private function editBook() {
		
	}
	
	/**
	 * Editiert einen Autor.
	 *
	 */
	private function editAuthor() {
		
	}
	
	/**
	 * Editiert einen Link.
	 *
	 */
	private function editLink() {
		
	}
	
	/**
	 * Löscht ein Buch.
	 *
	 */
	private function deleteBook() {
		
	}
	
	/**
	 * Löscht einen Autor.
	 *
	 */
	private function deleteAuthor() {
		
	}
	
	/**
	 * Löscht einen Link.
	 *
	 */
	private function deleteLink() {
		
	}	
	
}
?>

index.php
PHP:
<?php
require_once('content.class.php');

$oContent = new content(content::TYPE_BOOKS);

if($oContent->add('www.marc-binder.de', 'Marc Binder', 'www.marc-binder.de', 'Marc Binder')) {
	echo "Erfolg - das Buch wurde hinzugefügt.";
}
else {
	throw new Exception('Zu wenig Paramenter.');
}
?>

Ich hab das ganze soeben getestet und es funktioniert. Hoffe ich konnte dir damit helfen.

UPDATE:
Ach das hatte ich ganz vergessen. Und zwar, wenn du func_get_args(); verwendest, nimmt diese Funktion alle Variablen, die du verwendest, wenn du die Funktion aufrufst und erstellt daraus einen Array.
 
Zuletzt bearbeitet:
Vielen Dank für deine Mühe Marc, jedoch scheinst du mich mißverstanden zu haben. Ich möchte eine Abstraktion schreiben, also eine Klasse, die diese Aufgaben für jede beliebige Tabelle übernehmen kann. Durch Vererbung soll dann das ganze auf die spezifischen Tabellen übertragbar sein. das ist ja auch der eigentliche Sinn von OOP. Die Abstraktion von Abläufen, Beschreibungen und Verfahrensweisen. So ist es möglich Code zu sparen, da spezifischere Klassen vom abstrakten Bild erben. Vorhin wurde ja bereits erwähnt, dass die Magic Method __call() mir sehr helfen könnte. Was war damit gemeint? Die __call()-Methode wird immer dann aufgerufen, wenn eine aufgerufene Methode nicht existiert oder nicht verfügbar ist. So ist es möglich Methoden innerhalb einer Klasse verfügbar zu machen, die überhauptnicht existieren. Ein Beispiel.
PHP:
<?php
error_reporting(E_ALL | E_STRICT);

/**
* Class ActiveRecord
*
* The ActiveRecord class is a simple 
* MySqli-Mapper class. It enables the mapping 
* of tables to classes and rows to objects.
*  
* @package ActiveRecord
* @version 0.1
* @author Andreas Wilhelm <Andreas2209@web.de>
* @copyright Andreas Wilhelm
 */
class ActiveRecord
{
	// table fields
	$fields = array();

	/**
	* __call() - Sets the called Parameter
	*
	* @access public
	* @param Str $name
	* @param Arr $args
	* @return NONE
	*/
	public function __call($name, $args)
	{
		// get field name
		$field = substr($name, 2, -1);

		// make string lowercase
		$field = strtolower($field);

		if( (substr($name, 0, 2) != "set") )
				throw new Exception('Called undefined Method.');

		// check if field exists
		if( isset($this->fields[$field]) )
		{
			// check if given args are valid
			if( empty($args) )
				throw new Exception('Empty value.');
			if( count($args) > 1 )
				throw new Exception('To much values.');
			if( is_array($args[0]) || is_object($args[0]) )
				throw new Exception('Invalid value.');

			// everything all right assign value
			$this->fields[$field] = $args[0];
		}

		else
		{
			throw new Exception('Called undefined Method.');
		}
	}
}
?>
Mit dieser Klasse ist es möglich mit dem Aufruf
PHP:
$test = ActiveRecord();
$test->setName('Klaus');
$test->setAge(18);
Den Namen und das Alter in einer Zeile zu setzen, falls diese Felder in der Tabelle existieren. Mittels einer Methode save() könnte dies nun noch in eine Datenbank eingetragen werden. Ich hoffe ich konnte mein vorhaben etwas klarer machen.

Mein Problem ist noch, dass ich nicht weiß, wie ich der Klasse mitteile, an welcher Zeile ich momentan arbeiten, welche ich löschen oder anlegen möchte. Zudem weiß ich auch nicht, wie ich es am besten anstelle der Klasse mitzuteilen, dass ich gerne alle Datensätze ausgegeben haben möchte.
MfG, Andy
 
Hi, falls du eine MySQL Datenbank verwendest, würde ich ungefähr folgenden Weg empfehlen:

1. Du erstellst eine Klasse, die hinterher eine Tabelle der Datenbank abbildet.
2. Du übergibst der Klasse Konstruktor, als Parameter, den Namen der Sicht / Tabelle
3. Du ermmittelst mit der SQL Abfrage "DESCRIBE [{view}].{tablle}" die Struktur der Tabelle und speicherst sie im Objekt. (Dort solltest du nach Möglichkeit eine Cache Funktion einbauen, damit die Daten nicht jedesmal per SQL abgefragt werden müssen.)
4. Du erstellst eine 2. Klasse, die eine einzelne Zeile der Tabelle darstellt, dieser übergibst du im Konstruktor das Objekt, dass die Tabelle darstellt, somit kann immer ermittelt werden welche Felder die Tabelle hat und welche Datentypen sie haben.

Kurzes Beispiel:
PHP:
class Table
{
  protected $tableName;

  protected $fields = array();

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

    $result = mysql_query("DESCRIBE $tableName");

    while($data = mysql_fetch_assoc($result))
    {
      $this->fields[$data['Field']] = $data;
    }

    mysql_free_result($result);
  }

  public function hasField($field)
  {
    return array_key_exists($field, $this->fields);
  }

  public function createRow(array $data = array())
  {
    return new Row($this, $data);
  }
}

class Row
{
  protected $table;

  protected $data = array();

  public function __construct(Table $table, array $data = array())
  {
    $this->table = $table;

    foreach($data as $field => $value)
    {
      $this->__set($field, $value);
    }
  }

  public function __get($field)
  {
    if($this->table->hasField($field))
    {
      if(array_key_exists($field, $this->data))
      {
        return $this->data[$field];
      }
      else
      {
        return NULL;
      }
  }

  public function __set($field, $value)
  {
    if($this->table->hasField($field))
    {
      $this->data[$field] = $value;
    }
  }
}
Natürlich fehlen noch Methoden wie z.B. save(), aber ich denke der Ansatz müsste klar werden.
 
Zuletzt bearbeitet:
Zurück