- Ad -
IMPRESSUM dotted_line KONTAKT dotted_line search dotted_line Contact dotted_line sitemap
328 PHP-Resource Mitglieder online


php-resource



Zurück   PHP-Scripte PHP-Tutorials PHP-Jobs und vieles mehr > Blogs > AmicaNoctis
 

Login

 
eingeloggt bleiben
star Jetzt registrieren   star Passwort vergessen
 

Bewertung: 7 Stimmen mit einer durchschnittlichen Bewertung von 5,00.

Vererbung mit eingebauten Klassen

"Vererbung mit eingebauten Klassen" bei Mister Wong speichern "Vererbung mit eingebauten Klassen" bei YiGG.de speichern "Vererbung mit eingebauten Klassen" bei Google speichern "Vererbung mit eingebauten Klassen" bei del.icio.us speichern "Vererbung mit eingebauten Klassen" bei Digg speichern "Vererbung mit eingebauten Klassen" bei icio.de speichern "Vererbung mit eingebauten Klassen" bei My Yahoo speichern
Veröffentlicht: 28-04-2011 um 07:54 von AmicaNoctis
Aktualisiert: 28-04-2011 um 14:11 von AmicaNoctis

Um das hier halbwegs vernünftig lesen zu können, kopiere diesen Code in die Adressleiste und drücke Enter:

javascript:void((function(){for(i=0,d=document.getElementsByTagName("div");i<d.length;i++)d[i].style.width=="410px"&&(d[i].style.width="810px")})())

Um in den Code-Blöcken Zeilennummern zu sehen, mach dasselbe Spiel mit diesem Code:

javascript:void((function(){var a=1,b="#888",c="br",d="code",e="span";function g(c,d){c.insertBefore(document.createElement(e).appendChild(document.createTextNode((""+(1000+a++)). substr(1)+"| ")).parentNode,d).style.color=b;}function h(a,b){return a.nodeName.toLowerCase()==b;}function i(a){if(h(a,c))g(a.parentNode,a.nextSibling);else if(a.nodeType==1)for(var b=a.firstChild;b;b=b.nextSibling)i(b);}for(var j=0,f=document.getElementsByTagName(d);j<f.length;j++){var k=f[j];a=1;if(h(k.parentNode,d)){g(k,k.firstChild);i(k);}}})());



Die in PHP eingebauten Klassen sind schon oft hilfreich. Ja, sogar meistens. Aber manchmal reichen sie nicht aus und man braucht zusätzliche oder leicht veränderte Funktionalität.

Als OOP-Fans bedienen wir uns der Vererbung und fügen die gewünschten Features in der abgeleiteten Klasse hinzu. Das klappt oft prima. Ja, sogar meistens. Aber manchmal, nur manchmal, ist PHP ein Drecksack und verhält sich total unlogisch, weil irgendwelche verkorksten Interna anders funktionieren als man denkt, ja sogar anders als sie sollten.

Nein, ich will nicht nur Frust ablassen, sondern auch eine mögliche Lösung vorstellen, aber dazu später mehr. Erstmal kommt ein Beispiel:

Für ein bestimmtes Projekt brauchen wir präzise Zeitangaben mit Millisekunden. Die entsprechende Ableitung von DateTime namens PreciseDateTime haben wir bereits fertig und klappt soweit. Beim Addieren und Subtrahieren von Zeiten sind wir aber auf DateInterval beschränkt und diese Klasse arbeitet wiederum ohne Millisekunden-Anteil. Da das bisher alles so schön geklappt hat, schreiben wir auch hier eine abgeleitete Klasse PreciseDateInterval.

Wie die Elternklasse DateInterval selbst nimmt der Konstruktor einen auf den ersten Blick merkwürdig anmutenden String entgegen, in dem die Zeitspanne definiert wird: P8Y7M6DT5H43M21S
Das steht für Period: 8 Years 7 Months 6 Days Time part: 5 Hours 43 Minutes 21 Seconds. Die Reihenfolge ist zwar festgelegt, aber alle Bestandteile außer dem P sind optional. Wenn aber eine Zeitskomponente angegeben wird, muss das T davor stehen. Nur so kann man 3 Monate (P3M) von 3 Minuten (PT3M) unterscheiden.

Da wir ja mit Millisekunden arbeiten wollen, fügen wir noch das X ein, um z. B. 4 Minuten 3 Sekunden und 210 Millisekunden angeben zu können: PT4M3S210X
Also ran an die Arbeit und schnell was zusammengehackt, was schon recht gut aussieht. (Bitte die Quellcode-Kommentare lesen!)

PHP-Code:
class PreciseDateInterval extends DateInterval {
    public 
$ms 0;
    public function 
__construct ($pIntervalSpec) {
        
// pIntervalSpec fängt mit einem P an, dann kommen die optionalen Bestandteile
        // in genau dieser Reihenfolge:
        // ..Y Anzahl Jahre
        // ..M Anzahl Monate
        // ..W Anzahl Wochen
        // ..D Anzahl Tage (wenn W angegeben ist wird D einfach ignoriert)
        // T   Trennzeichen - ist Pflicht, wenn eine Zeitkomponente vorhanden ist
        // ..H Anzahl Stunden
        // ..M Anzahl Minuten
        // ..S Anzahl Sekunden
        // ..X Anzahl Millisekunden (haben wir hinzuerfunden)
        
        // Das ganze muss jetzt aufgedröselt werden, denn der Elternkonstruktor würde über
        // den X-Teil meckern aber wir brauchen ihn, um die zusätzliche eigene Eigenschaft $ms
        // zu setzen.
        //                                                _______________________________
        //                                               /5                       ______ \
        //               _____   _____   _____   _____  / _____   _____   _____  |9____ \ \
        // Subpatterns: /1    \ /2    \ /3    \ /4    \/ /6    \ /7    \ /8    \ |/10  \ \ \
        
$pattern =    "P(\\d+Y)?(\\d+M)?(\\d+W)?(\\d+D)(T(\\d+H)?(\\d+M)?(\\d+S)?((\\d+)X)?)?";

        
// neue intervalSpec für den Elternkonstruktor zusammenbauen
        
$spec "P";
        if (
preg_match("<^" $pattern "$>"$pIntervalSpec$matches)) {
            foreach (
$matches as $i => $match) {
                if (
$i == 5) {
                    
// die 5 umfasst alle folgenden, also auch den X-Teil,
                    // daher nehmen wir nur das T mit und grasen 6 bis 8 einzeln ab
                    
$spec .= "T";
                }
                else if (
$i && $i 9) {
                    
// den gesamten Ausdruck (0) und den X-Teil (9 und 10) ausschließen
                    // die anderen (außer der 5) werden rüberkopiert
                    
$spec .= $match;
                }
                else if (
$i == 10) {
                    
// wir greifen uns den X-Teil, aber dazu kommen wir später
                    // $this->ms = (int) $match;
                
}
            }
        }
        else {
            
// Regex hat nicht gematcht => pIntervalSpec ungültig
            // soll sich der Elternkonstruktor damit rumschlagen
            
$spec $pIntervalSpec;
        }
        
        
parent::__construct($spec);
    }
    public function 
format ($pFormat) {
        
// hier passiert die Magie, dass neben den eingebauten Tokens für die Ausgabe
        // %Y, %M, %D, %H, %I, %S, %R, 
        // %y, %m, %d, %h, %i, %s, %r und %a
        // jetzt auch %X unterstützt wird, um an die Millisekunden ranzukommen
        
$result "";
        
$isLiteral false;
        foreach (
explode("X"$pFormat) as $k => $v) {
            
$vt rtrim($v"%");
            
$percents strlen($v) - strlen($vt);
            if (
$k) {
                
$result .= $isLiteral "X" str_pad($this->ms30STR_PAD_LEFT);
            }
            
$result .= parent::format($vt) . str_repeat("%"floor($percents 2));
            
$isLiteral = !($percents 2);
        }
        return 
$result;
    }

Wir legen los mit einem kleinen Testlauf

PHP-Code:
header("Content-Type: text/plain; charset=utf-8");
print_r(new PreciseDateInterval("P8Y7M6DT5H43M21S987X")); 
und erhalten schonmal das hier:
Code:
PreciseDateInterval Object
(
    [ms] => 0
    [y] => 8
    [m] => 7
    [d] => 6
    [h] => 5
    [i] => 43
    [s] => 21
    [invert] => 0
    [days] => 
)
Da wir Zeile 41 noch auskommentiert haben, ist es kein Wunder, dass der Millisekunden-Anteil noch auf 0 steht. Also den Kommentar schnell wegnehmen und...

Zitat:
Fatal error: PreciseDateInterval::__construct(): Unknown property (ms) in ... on line 41
Was soll das denn? Die ist sogar public! Wieso können wir nicht darauf zugreifen?

DateInterval ist eine von diesen Klassen, mit denen man keine anständige Vererbung hinbekommt, weil intern das OOP nur trickreich vorgegaukelt wird, die Klasse aber gar kein OOP kann. Der gleiche Betrug wie beim Zitronenfalter.





Wie lösen wir das Problem? Mit Delegation. Dabei speichern wir eine Instanz in einer privaten Eigenschaft und reichen Methodenaufrufe an diese durch. Das kann man durch die magische __call-Methode natürlich stark vereinfachen, anstatt für jede Methode eine eigene zu schreiben, die diese aufruft. Für Eigenschaften gilt dasselbe: Mittels __get und __set delegieren wir das an die gespeicherte Instanz.

PHP-Code:
class PreciseDateInterval {
    private 
$interval null;
    public  
$ms 0;
    public function 
__construct ($pIntervalSpec) {
        
$pattern =    "P(\\d+Y)?(\\d+M)?(\\d+W)?(\\d+D)(T(\\d+H)?(\\d+M)?(\\d+S)?((\\d+)X)?)?";
        
$spec "P";
        if (
preg_match("<^" $pattern "$>"$pIntervalSpec$matches)) {
            foreach (
$matches as $i => $match) {
                if (
$i == 5) {
                    
$spec .= "T";
                }
                else if (
$i && $i 9) {
                    
$spec .= $match;
                }
                else if (
$i == 10) {
                    
$this->ms = (int) $match;
                }
            }
        }
        else {
            
$spec $pIntervalSpec;
        }
        
// bis hierher alles wie gehabt, aber statt des Elternkonstruktors erzeugen und
        // speichern wir eine Instanz
        
$this->interval = new DateInterval($spec);
    }
    
    
// mit magischen Gettern und Settern kann man dann alle abgerufenen Eigenschaften an die
    // gespeicherte Instanz durchreichen. __call brauchen wir nicht, da die einzige Methode
    // format() ist und wir die selbst umimplementieren müssen.
    
public function __get ($pName) {
        return 
$this->interval->$pName;
    }
    public function 
__set ($pName$pValue) {
        
$this->interval->$pName $pValue;
    }
    
    public function 
format ($pFormat) {
        
$result "";
        
$isLiteral false;
        foreach (
explode("X"$pFormat) as $k => $v) {
            
$vt rtrim($v"%");
            
$percents strlen($v) - strlen($vt);
            if (
$k$result .= $isLiteral "X" str_pad($this->ms30STR_PAD_LEFT);
            
$result .= $this->interval->format($vt) . str_repeat("%"floor($percents 2));
            
$isLiteral = !($percents 2);
        }
        return 
$result;
    }

Also dann, ein neuer Versuch:

PHP-Code:
header("Content-Type: text/plain; charset=utf-8");
$pdi = new PreciseDateInterval("P8Y7M6DT5H43M21S987X");
$pdi->1;
echo 
$pdi->s","$pdi->ms"\n";
echo 
$pdi->format("%M:%S.%X"); 
Wir erhalten erwartungsgemäß

Code:
21,987
01:21.987
also hat das mit dem Delegieren geklappt. Beim Setzen der Minuten auf 1 wurde es an die DateInterval-Instanz weitergegeben.

Während man in anderen Fällen durchaus trotzdem von der Delegat-Klasse erben kann, um auch bei Typehinting die Anforderungen an die Methodensignatur zu erfüllen, geht das in diesem speziellen Falle nicht. Sobald wir angeben, dass wir von DateInterval erben, können wir wieder nicht mehr auf die eigenen Membervariablen zugreifen.

Geht es aber z. B. darum, eine Ableitung von DomElement zu schreiben, um das Verhalten von firstChild, nextSibling u. s. w. zu modifizieren (was auch nur über Delegation möglich ist), dann können wir trotzdem von DomElement erben und dank DomDocument::registerNodeClass können wir das DomDocument anweisen, immer unsere Klasse zu verwenden.

Digg this Post! Add Post to del.icio.us Bookmark Post in Technorati Furl this Post!
Hits 4965 Kommentare 4 Stichworte bearbeiten Hinweis auf diesen Blog-Eintrag per E-Mail verschicken
« Zurück     Startseite des Blogs     Nächste »
Kommentare 4

Kommentare

  1. Alter Kommentar
    Thank you for nice post. You explained how DateInterval and this class works without a millisecond fraction very well. On particular when I usually do my papers I often get confused with derived class PreciseDateInterval, but you have clarified that classes built into PHP are often in a need of additional or slightly changed functionality. Like on the example provided.
    permalink
    Veröffentlicht: 12-05-2017 um 08:46 von JenifferMurphy JenifferMurphy ist offline
  2. Alter Kommentar
    Hi, your blog is truly flawless and unique. Good content, but it would be better if in future you can talk about more about this subject. Really very perfect your article and blog post.
    a10, color switch, animal jam
    permalink
    Veröffentlicht: 05-10-2017 um 04:42 von a10games a10games ist offline
  3. Alter Kommentar
    Your sharing of this content is very interesting. I like it very much. Hopefully I will be able to read more post from you.

    Lily Oliver | happy wheels
    permalink
    Veröffentlicht: 28-10-2017 um 11:40 von lilyoliver lilyoliver ist offline
  4. Alter Kommentar
    Oh yes the problem i need to find. Yourarticle provides useful information that i am looking for. I will often visitthis site
    amazon contact live person
    permalink
    Veröffentlicht: 05-11-2017 um 12:42 von Kortgdde Kortgdde ist offline
 
Trackbacks 0

Trackbacks


Alle Zeitangaben in WEZ +2. Es ist jetzt 01:43 Uhr.



Search Engine Friendly URLs by vBSEO 3.3.0