PHP Zwei Methoden auf ein Objekt anwenden

jopjip

Ensign
Registriert
Juni 2008
Beiträge
233
Hi,
ich habe eine Frage zur Kombination verschiederner Methoden einer Klasse. Wie kann ich zwei Methoden einer Klasse möglichst elegant auf ein Objekt anwenden?
Der folgende Code, bewirkt, dass das Objekt in die Tags <i></i> oder <b></b> geschrieben wird. Wie erreiche ich eine Kombination oder beiden Methoden, sodass am Ende folgendes rauskommt:
HTML:
<i><b>Ein Text</b></i>

Der Code unten bewirkt, was ich eben beschrieben habe, allerdings muss ein Umweg über ein zweites Objekt gegangen werden.
Geht das auch eleganter?
Hier erstmal der Code:
PHP:
class mache_kursiv
{
	public $text;
	protected $kursiv = "<i>@</i>";
	
	public function __construct($text)
	{
		$this->text = $text;
	}
	
	public function kursiv()
	{
		return str_replace("@", $this->text, $this->kursiv);
	}
}
class mache_fett extends mache_kursiv
{
	protected $fett = "<b>@</b>";
	
	public function __construct($text)
	{
		parent::__construct($text);
	}
	
	public function fett()
	{
		return str_replace("@", $this->text, $this->fett);
	}
}

//1. Objekt wird fett geschrieben
$object = new mache_fett("Ein Text");

$text = $object->kursiv();
//2. Objekt das fettgeschriebene wird noch kursiv geschrieben
$object2 = new mache_fett($text);
echo $object2->fett();

Das Skript gibt folgende Ausgabe: Ein Text
Wie gesagt, dieser Weg umschließt einen Umweg und der folgende Code bewirkt eine Fehlermeldung:
PHP:
$object->kursiv()->fett();
Also wie wird es richtig gemacht? Ich hoffe ihr konntet verstehen, was ich meine.

Grüße
 
Ist es nicht einfacher CSS anzuwenden?
Eine Klasse in CSS erstellen mit
.fettkursiv {
font-style:italic;
font-size:110%;
}


Ich weiss jetz nich ob italic genau das Attribut fuer kursiv ist, aber musste mal gucken, laesst sich ganz schnell finden ;)
 
Du willst nur die Funktionen Kursiv und Fett haben?

Na dann:

PHP:
function kursiv($text)
{
return ("<i>".$text."</i>");
}
und
PHP:
function fett($text)
{
return ("<b>".$text."</b>");
}
Aufruf:
PHP:
print kursiv("TEXT");

beides:
PHP:
print kursiv(fett(("TEXT2"));
 
Ihr habt das total falsch verstanden. Es geht hier nicht um CSS-Klassen sondern um ein Prinzip im Bereich OOP, dass an dem kursiv/fett-Beispiel erläuert werden soll.

Ich versuche mal nachzubessern. Ich definieren zwei Methoden in einer PHP-KLasse und möchte beide auf ein Objekt anwenden. Also:
PHP:
$objekt->kursiv()->fett();
Ich habe die zwei functions in zwei Klassen geschrieben, aber selbst wenn ich einer Klasse zwei public functions defnieren funktioniert das nicht, wie ich es mir vorstelle:

PHP:
class mache_format
{
	public $text;
	protected $kursiv = "<i>@</i>";
	
	public function __construct($text)
	{
		$this->text = $text;
	}
	
	public function kursiv()
	{
		return str_replace("@", $this->text, $this->kursiv);
	}

	public function fett()
	{
		return str_replace("@", $this->text, $this->fett);
	}

}

Und der Befehl:
PHP:
$object = new mache_format("Ein Text");

echo $objekt->kursiv()->fett();

Gibt eine Fehlermeldung. Versteht ihr? Ich möchte beide Methoden der Klasse auf ein Objekt anwenden ohne vorher ein neues erstellen zu müssen. Ich weiß in diesem Beispiel wäre viel einfacher eine neue Klasse zu schreiben, etc. blalbla aber darum geht es mit nicht sondern um das Prinzip, das dahinter steht.

MFG
 
So würde es gehen:

PHP:
class mache_format
{
    protected $text; // kein Grund, das public zu machen
    protected $fett = "<b>@</b>";  // das fehlte in deinem Beispiel noch
    protected $kursiv = "<i>@</i>";
    
    public function __construct($text)
    {
        $this->text = $text;
    }
    
    public function kursiv()
    {
        $this->text =  str_replace("@", $this->text, $this->kursiv);
        return $this;
    }

    public function fett()
    {
        $this->text = str_replace("@", $this->text, $this->fett);
        return $this;
    }

    public function ausgeben()
    {
        return $this->text;
    }
}

// Aufruf
$object = new mache_format("Ein Text");

echo $objekt->kursiv()->fett()->ausgeben();

Der Schlüssel zum Erfolg ist das "return $this" in den Methoden kursiv() und fett(). Dadurch, daß das Objekt selbst zurückgegeben wird, kannst du auf diesem Rückgabewert direkt weitere Methoden aufrufen. Diese Technik wird auch als "Fluent Interface" bezeichnet. In deinem Code gab kursiv() einen String zurück - der kennt natürlich keine Methode fett().

Wenn du dir den Aufruf von ausgeben() sparen willst und eine aktuelle PHP-Version verwendest, kannst du auch die "magische Methode" __toString() einsetzen:

PHP:
// in mache_format:
    public function __toString()
    {
        return $this->text;
    }

// Aufruf
$object = new mache_format("Ein Text");

echo $objekt->kursiv()->fett();

Wenn man echo als Argument ein Objekt übergibt, das eine __toString()-Methode hat, dann wird diese automatisch aufgerufen und das Ergebnis ausgegeben. In Fällen wie diesem sehr praktisch :)
 
Zuletzt bearbeitet:
Vielen Dank! Genau das habe ich gemeint ;-)
 
NullPointer schrieb:
Der Schlüssel zum Erfolg ist das "return $this" in den Methoden kursiv() und fett(). Dadurch, daß das Objekt selbst zurückgegeben wird, kannst du auf diesem Rückgabewert direkt weitere Methoden aufrufen. Diese Technik wird auch als "Fluent Interface" bezeichnet.

Deine Ausführungen sind im Hinblick auf die Frage des TS völlig korrekt, über die Begrifflichkeiten könnte man aber noch diskutieren. Bei dem von dir beschriebenen Konzept handelt es sich imho zunächst mal nur um einfaches Method Chaining - ein Fluent Interface ist zwar eine konkrete Anwendung des Method Chaining, aber da gehört imho noch etwas mehr dazu als nur die Verkettung von Methodenaufrufen, wie Beispielsweise das Vorhandensein einer Grammatik. Derartiges findet man gerne in Test-Frameworks oder OR-Mapper-APIs, z.B. Database.select("...").from("...").where("...").asList().

edit: siehe hierzu Martin Fowler, der den Begriff damals geprägt hat:

I've also noticed a common misconception - many people seem to equate fluent interfaces with Method Chaining. Certainly chaining is a common technique to use with fluent interfaces, but true fluency is much more than that.

http://martinfowler.com/bliki/FluentInterface.html

Beim Beispiel des TS wäre außerdem noch zu überlegen, ob durch $object->kursiv()->fett() wirklich der Zustand des original-Objekts verändert werden sollte, oder ob man $object nicht besser als immutable ansieht und ein neues Objekt zurückgibt. Vgl. Strings in Java:

Code:
String A = "ABC";
String B = A.replace('B', 'X').substring(1).toLowerCase();

// A: ABC
// B: xc

Aber das führt an dieser Stelle zu weit und hängt vom Szenario ab :)
 
Zuletzt bearbeitet:
Ich hab zu der Angelegenheit auch noch eine Frage, evtl. handelt es sich hier auch wieder um Grundsätze des objektorientierten Programmierens.

Ich bastle gerade an einer Klasse, die derzeit folgendermaßen aussieht (ich habe die Klasse hier zur Übersicht mal gerade etwas gekürzt):

PHP:
class mysqldb extends mysqli {

	public function __construct()
	{	
		// DB Verbindung herstellen
	}
	
	public function query ($sql)
	{
		$this->result = parent::query($sql);
		return $this;
	}
	
	public function getAll ()
	{		
		while ($row = $this->result->fetch_array())
		{
			$data[] = $row;
		}
		return $data;
	}

Die Methode $query soll eine Datenbankanfrage durchführen können; die Methode getAll() soll alle Datensätze, die in einer Query entstanden sind, in Array Form ausgeben können. Soweit so gut.

Ich lade anschließend die Klasse und möchte zwei Queries an die Datenbank senden, die voneinander unabhängig sind. Z.B. so:

PHP:
$mysqldb = new mysqldb();

$result_a = $mysqldb->query('SELECT blubb');
$result_b = $mysqldb->query('SELECT bla');

// Die Datensätze rufe ich jetzt hintereinander ab; mögliche Szenarien wäre auch ein Aufruf, der ineinander verschachtelt wäre.

$data_a = $result_a->getAll();
$data_b = $result_b->getAll();

var_dump($data_a);
var_dump($data_b);

Aber hoppla! $data_a hat kein Inhalt. Nur der Array $data_b ist mit Daten gefüllt.

Meine Interpretation: Die Zeile
PHP:
 $result_b = $mysqldb->query('SELECT bla');
überschreibt das, was in der Zeile zuvor
PHP:
$result_a = $mysqldb->query('SELECT blubb');
abgefragt wurde, weil der Wert in der Methode mittels
PHP:
$this->result = parent::query($sql);
gespeichert wurde.

Ich steh da gerade irgendwie auf dem Schlauch. Ich glaube, das ist ja auch nicht Sinn und Zweck der objektorientierten Programmierung? Mein Ziel ist es, dass ich die Methode getAll() jederzeit auf jedes Result-Objekt anwenden kann, und ich die richtigen Daten dazu abrufen kann. Bei meiner jetzigen Klasse kann ich mit getAll() aber immer nur die Daten der zuletzt durchgeführten Query abrufen, was mir sämtliche Verschachtelungen unmöglich machen würde... Irgendwo ist da bei mir ein Denkfehler drin? Wie muss ich die Methode getAll() gestalten, damit sie sich auf das Objekt ($result_a oder $result_b) bezieht, mit dem sie aufgerufen wird, und nicht auf das $this->result?

Logischerweise will ich das Objekt auch nicht zusätzlich in Klammern übergeben a la:
PHP:
$data_b = $result_b->getAll($result_b);
... das wäre irgendwie doppelt gemoppelt und in meinen Augen nur ein Work-Around des Ganzen und dann könnte ich gleich auf funktionale Programmierweise umsteigen...

Ich hoffe mir kann jemand weiterhelfen...

EDIT: Oder müsste ich pro Query eine neue Instanz der Klasse laden? Aber irgendwie kommt mir das auch schleierhaft vor? Der DB-Connect würde dann ebenfalls bei jeder Query neu ausgeführt werden...

EDIT2: Wenn ich das ganze prozedural aufrufe funktionierts, siehe:

PHP:
$mysqli = new mysqli("localhost", "user", "pass", "db");

$result_a = $mysqli->query('SELECT blubb');
$result_b = $mysqli->query('SELECT bla');

while ($row_a = $result_a->fetch_array())
{
	$data_a[] = $row_a;
}

while ($row_b = $result_b->fetch_array())
{
	$data_b[] = $row_b;
}

print_r($data_a);
print_r($data_b);

Beide Arrays, also $data_a und $data_b sind mit dem korrekten Inhalt gefüllt. Die obige selbstgeschriebene Variante getAll(), die nach dem selben Prinzip funktionieren soll, scheitert leider :(
 
Zuletzt bearbeitet:
Hi Drexel,

vielen Dank für das Stichwort. Ich bin gerade über ein paar Codebeispiele gestolpert, die ziemlich ähnliche Problematiken besprechen. Leider komme ich da allerdings nicht voran...

Mein Grundgedanke ist, dass ich das Codebeispiel der php Website genauso auch verwenden möchte mit meiner Methode getAll();

PHP:
$query = "SELECT Name, CountryCode FROM City ORDER by ID LIMIT 3";
$result = $mysqli->query($query);
$row = $result->fetch_array();
(ich habs der übersichthalber etwas gekürzt, den ganzen Code gibts auf http://de2.php.net/manual/en/mysqli-result.fetch-array.php nachzulesen).

Also sprich, wie muss ich meine Methode getAll(); definieren, damit ich sie genau wie das ->fetch_array(); verwenden kann? Muss ich in diesem Fall auch schon zusätzliche Klassen und Interfaces erstellen? Zur Übung würde es sich im Moment nur um diese eine Methode handeln, erst später sollen da noch weitere Methoden dazu kommen...
 
Also mit Deinem Problem hat das Decorator Pattern nichts zu tun, eher mit dem problem des Threaderstellers. Was Deine Klasse da macht ist eh ein bißchen seltsam. Durch $this->result = parent::query($sql); in der Query Methode speicherst Du immer das Ergebnis des letzten Queries im result property deines Objekts. und mit return this; gibst Du Dein Objekt zurück. in data_a und b sollte dann eigentlich das gleiche stehen.

Mir ist aber auch nicht klar, warum Du die mysqli Klassen nochmal erweiterst, es scheint mir in Deinem Beispiel keinen Mehrwert zu geben. Dein prozeduraler Code nutzt insofern objektorientierung, dass Du die mysqli Klasse verwendest, wenn Du den Code jetzt noch in eine Methode einer Datenbank Access Klasse zum Beispiel packst ist doch alles gut.

So ungefähr z.B.:

Code:
    class DbAccess {
     
    public function __construct()
    {	
    //mysqli Objekte instanzieren, soweit es Member werden sollen
    }
    public function getBla (...)
    {
      ...
    }

    public function getBlubb (...)
    {
      ...
    }
  
    }
 
Zurück
Oben