Seit paar Tagen optimiere und refactoriere ich eine Komponente die bereits vor einem Jahr von PHP4 auf PHP5 umgestellt und refactoriert wurde. Dabei bin ich auf viele lustige Codefragmente gestoßen. Eins hat bei mir für viel Aufmerksamkeit gesorgt. Und zwar: innerhalb einer Methode wurden Variablen die nur in dieser Methode verwendet werden als „static“ Deklariert. Hmmm…, was soll das den bitte? Ist es ein Feature? Wenn ja, wo ist der Verteil? So habe ich mich auf der Suche gemacht. Hier ein Beispiel aus der Komponente:

class SelectBox
{
    public function getTimeOptions()
    {
        static $hours, $mins, $options;

        $hours = range(0, 23, 1);
        $mins  = range(0, 45, 15);

        foreach ($hours as $hour)
        {
            foreach ($mins as $min)
            {
                $hour = ($hour == 0) ? '00' : $hour;
                $hour = (mb_strlen($hour) == 1) ? '0'.$hour : $hour;
                $min = ($min == 0) ? '00' : $min;

                $options[] = $hour.':'.$min;
            }
        }

      return $options;
    }
}

In den oben gezeigtem Beispiel ist es deutlich zu sehen, dass die alte Deklaration „global“ einfach durch „static“ ersetzt worden ist. Ob an diese Stelle richtig refactoriert worden ist, sei es mal hingestellt. Die Methode „getTimeOptions“ soll ein Array zurück liefern das alle Uhrzeiten zwischen 00 Uhr und 23 Uhr in 15 Minuten Schritte beinhaltet. Also, ein Array wie das hier:

Array
(
    [0] => 00:00
    [1] => 00:15
    [2] => 00:30
    [3] => 00:45
    [4] => 01:00
    [5] => 01:15
    [6] => 01:30
    [7] => 01:45
    [8] => 02:00
    [9] => 02:15
    [10] => 02:30
    [11] => 02:45
    [12] => 03:00
    [13] => 03:15
    [14] => 03:30
    [15] => 03:45
    [16] => 04:00
    ...
    ..
    .

Nun habe ich mich genau über die „static“ Deklaration informiert und erfahren, dass auch in der Programmierung eine Variable als ein Akkumulator verwendet werden kann. Man kann sie also aufladen und entladen. Ist also besonders gut geeignet um Zwischenergebnisse, innerhalb der Laufzeit eines Prozesses abzulegen. Solche Akkumulatoren sollen auch in Schleifen innerhalb von PHP selbst verwendet werden. Hmm…, das klingt spannend! Also eine Art Cache bzw. Zwischenspeicher. Kurzerhand habe ich die Klasse umgebaut. Hier die Klasse nach dem ersten Umbau:

class SelectBox
{
    public function getTimeOptions()
    {
        static $options;

        var_dump(empty($options));

        if (true === empty($options))
        {
            $hours = range(0, 23, 1);
            $mins  = range(0, 45, 15);

            foreach ($hours as $hour)
            {
                foreach ($mins as $min)
                {
                    $hour = ($hour == 0) ? '00' : $hour;
                    $hour = (mb_strlen($hour) == 1) ? '0'.$hour : $hour;
                    $min = ($min == 0) ? '00' : $min;

                    $options[] = $hour.':'.$min;
                }
            }
        }

        return $options;
    }

}

class CheckBox
extends SelectBox
{

}

Den Aufruf „var_dump(empty($options));“ habe ich absichtlich eingebaut um die Auswirkung von unseren Akkumulator deutlicher zu machen. Zudem habe ich die Klasse mit der „CheckBox“ Klasse erweitert. Einfach für testzwecke. Ich weiß ja nicht was mich erwartet. Hier der Test:

$selectBox = new SelectBox();
print 'Instance of SelectBox'.PHP_EOL;
print 'CALL 1:';$selectBox->getTimeOptions();
print 'CALL 2:';$selectBox->getTimeOptions();
print 'CALL 3:';$selectBox->getTimeOptions();
print '----------------------------'.PHP_EOL;

$selectBox = new SelectBox();
print 'Instance of SelectBox'.PHP_EOL;
print 'CALL 1:';$selectBox->getTimeOptions();
print 'CALL 2:';$selectBox->getTimeOptions();
print 'CALL 3:';$selectBox->getTimeOptions();
print '----------------------------'.PHP_EOL;

$checkBox = new CheckBox();
print 'Instance of CheckBox'.PHP_EOL;
print 'CALL 1:';$checkBox->getTimeOptions();
print 'CALL 2:';$checkBox->getTimeOptions();
print 'CALL 3:';$checkBox->getTimeOptions();
print '----------------------------'.PHP_EOL;

Hier die Ausgabe:

Instance of SelectBox
CALL 1:bool(true)
CALL 2:bool(false)
CALL 3:bool(false)
----------------------------
Instance of SelectBox
CALL 1:bool(false)
CALL 2:bool(false)
CALL 3:bool(false)
----------------------------
Instance of CheckBox
CALL 1:bool(true)
CALL 2:bool(false)
CALL 3:bool(false)
----------------------------

Das Ergebnis ist nicht verblüffend. Beim aller ersten Aufruf der Methode wird die Berechnung der Zeit-Optionen ausgeführt und in dem Akkumulator gespeichert. Bei jedem weiterem Aufruf der Methode wird direkt auf dem Akkumulator zugegriffen. Auch wenn die Instanz von SelectBox neu erzeugt wurde. Bei der Instanz von CheckBox, die SelectBox erweitert, müssen die Zeit-Optionen wiederum neu berechnet werden. Ein Vorteil hat das nur wenn man sicher ist, dass eine Berechnung lange dauert und dasselbe Ergebnis im gesamten Prozess zurück liefert. Den Einsatz von der „static“ Deklaration betrachte ich mit gemischten Gefühlen. Einerseits hat das nichts mehr in der Objekt-Orientierten-Programmierung zu suchen, andererseits kann es doch nützlich sein, wenn man genau weiß wie es eingesetzt werden kann.

Hier habe ich ein endgültiges refactoring vollzogen. Denn mir war wichtig, dass die Methode flexibler wird, und mit anderen Uhrzeit- und Minuten-Abstände arbeiten kann.

class SelectBox
{
    public function getTimeOptions(array $hours, array $mins)
    {
        static $options;

        if (true === empty($options))
        {
            foreach ($hours as $hour)
            {
                foreach ($mins as $min)
                {
                    $hour = (mb_strlen($hour) == 1) ? '0'.$hour : $hour;
                    $min = ($min == 0) ? '00' : $min;

                    $options[] = $hour.':'.$min;
                }
            }
        }

        return $options;
    }
}

$hours = range(0, 23, 1);
$mins  = range(0, 45, 15);
$selectBox = new SelectBox();
$selectBox->getTimeOptions($hours, $mins);

16 thoughts on “Static-Variablen in Methoden

  1. In Funktionen hab ich static schon das ein oder andere mal benutzt.
    Bei Objekten könnte die $options-Variable aber auch als (statische) Eigenschaft deklariert und mit $this->options genutzt (bzw. gecached) werden.
    Dadurch ist sie im gesamten Objekt – und bei Bedarf auch nach aussen – zugreifbar. Ob das in diesem Fall sinnvoll ist ist eine andere Frage :)

  2. Wie sieht es hier aus, wenn man mehrere instanzen des Objekts hat? Dann gibts doch Probleme, weil alle Instanzen die gleiche static variable benutzen?
    Wie ich das sehe wird die Variable an den jeweiligen Namespace der Klasse gebunden.

    — Kann das Probleme bei der Vererbung machen? —

    Generell halte ich nicht viel von dieser Technik, wieso nicht einfach eine statische methode für getOptions schreiben?

    Diese Vermischung von static und non-static ist doch verwirrend. ?!?

  3. @Thomas:
    Wie meinst du das mit der $options-Variable (statisch) zu deklarieren?

    class Test
    {
     public static $option;
    
     public function setSelf( $val ){
      self::$option = $val;
     }
    
     public function setThis( $val ){
      $this->option = $val;
     }
    
     public function getSelf(){
      return self:$option;
     }
    
     public function getThis(){
      return $this->option;
     }
    }
    

    So in etwa? Also bei mir funktioniert das nicht. getSelf und getThis würden zwei unterschiedliche Werte zurück geben. Wenn du $option mit setSelf setzt, kannst du nicht mit getThis darauf zugreifen.

  4. > Wie sieht es hier aus, wenn man mehrere instanzen des Objekts hat? Dann gibts doch Probleme, weil alle Instanzen die gleiche static variable benutzen?

    Tatsache, Objektübergreifendes Datenhandling! Cool, noch nie drüber nachgedacht.

    get ()); // int(1805) 
    var_dump ($f1->get ()); // int(1805) 
    
    $f2 = new Foo;
    var_dump ($f2->get ()); // int(1805) 
    
    
    
    @Artikel:
    
    $hour = ($hour == 0) ? '00' : $hour;  
    $hour = (mb_strlen($hour) == 1) ? '0'.$hour : $hour;  
    

    Doppelt gemoppelt!
    Vorschlag:

    if(1 == mb_strlen($hour)){
      $hour = '0' . $hour;  
    }
    
  5. Gerade in diesem Fall wäre es doch einfach, das stattdessen mit einer Objekt-Eigenschaft zu lösen, oder nicht?

    class SelectBox{
      private $timeOptions;
    
      private function createTimeOptions(){ /* Creates $this->options. */ }
    
      public function getTimeOptions(){
        if (!$this->options){
          $this->createTimeOptions();}
        return $this->timeOptions;}
    
      /* Rest */ }
    

    Statische Eigenschaften in Klassen und statische Variablen in Funktionen/Methoden sind mir suspekt, weil sie letztlich nichts anderes als Global state bilden, wenn auch gut versteckt.

  6. @nik: Manche Codefragmente habe ich mit Absicht so gelassen, denn sie funktionieren ja. Zudem sollte die Klasse refactored werden und nicht komplett neu geschrieben werden. Sie sollte also flexibler werden.

    @all: Mit diesem Artikel wollte ich nur eine positive Eigenschaft von static-Deklaration von Variablen in Methoden, vorstellen. Bei einer Neuentwicklung würde ich komplett auf static verzichten. Also keine static Klassen, Methoden und Variablen. Sie erschweren das Testen und können zu drastischen Problemen sowie Verwirrung führen, wenn sie nicht richtig eingesetzt werden.

  7. Im letzten Refactoring müsste es doch jetzt son sein, dass folgender Aufruf

    $selectBox = new SelectBox();  
    $selectBox->getTimeOptions($hours1, $mins1);  
    $selectBox->getTimeOptions($hours2, $mins2);  
    

    beides mal das gleiche Ergebnis liefert, obwohl andere Parameter übergeben werden, weil ja die static Variable schon gefüllt ist und so die Berechnung nicht erneut durchgeführt wird.

    Oder hab ich was übersehen?

  8. @j123456: ja, das ist Absicht. Denn innerhalb des Prozesses sollen die Zeit-Optionen nur einmal gesetzt werden. Wenn es nach mir ginge, dann würde ich das etwa so umsetzten:

    class SelectBox
    {
        public function getTimeOptions(array $hours, array $mins)
        {
            $options = array();
    
            foreach ($hours as $hour)
            {
                foreach ($mins as $min)
                {
                    $hour = ($hour == 0) ? '00' : $hour;
                    $hour = (mb_strlen($hour) == 1) ? '0'.$hour : $hour;
                    $min = ($min == 0) ? '00' : $min;
    
                    $options[] = $hour.':'.$min;
                }
            }
    
            return $options;
        }
    }
    
  9. Aber, wenn ich folgende Parameter reingebe,
    möchte ich doch auch entsprechend andere Returnwerte haben.

    $hours1= array(1,2,3); #Result 01:xx, 02:xx …
    $hours2= array(3,4,5); #Result 03:xx, 04:xx …

    Mit dem Code aus dem letztem Post, passiert dies natürlich nicht mehr.

    Weiterhin müsste die erste Zeile bei Folgendem schon durch die 2te Zeile abgedeckt sein.
    $hour = ($hour == 0) ? ’00‘ : $hour;
    $hour = (mb_strlen($hour) == 1) ? ‚0‘.$hour : $hour;

    1. @j123456: ja, da gebe ich dir volkommen Recht dass „$hour = ($hour == 0) ? ’00‘ : $hour;“ in diesem Fall überflüssig ist. Ich wollte eigentlich nicht soweit refactorierien, aber ich ziehe das gerne nach. Danke dir :-)

  10. Nachtrag: Natürlich meinte, ich dass mit dem Code aus dem letzten Post, es richtig funktioniert.
    Aber da ist ja auch das static, etc. rausgefallen.

  11. es geht zwar um was anderes, aber in der letzten version ist widrs die ausgabe fuer ein minuteninterval<10 kaputt. was ja aber das ziel der letzten ueberarbeitung war.
    vllt. doch eine grundlegende loesung mit sprintf oder number_format?

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.