"The greatest wealth is to live content with little" --Plato

Statický konstruktor

Aug 10, 2008 | koubel | php

Když jsem nedávno psal nějakou třídičku v PHP tak jsem narazil na dost častou záležitost, použil jsem statickou proměnnou na definování implicitní hodnoty a další nestatickou proměnnou, která umožňuje nastavit danou vlastnost pokud od třídy dědíme. Vypadá to asi nějak takto.

class MyClass
{
  /** variable with default value */
  private static $_defaultVariable;
 
  /** variable for an usage with an inheritance */
  private $_variable;
}

Předpokládané použití je takové, že statická implicitní vlastnost bude v aplikaci, která třídu používá dostupná tak nějak "automaticky", pokud budeme vytvářet potomky třídy, tak si jí podle potřeby můžeme dynamicky modifikovat. Jak teď ale na to implicitní inicializaci. V PHP neexistuje nějaký jasný prostředek, jak inicializovat statické proměnné tříd - statické konstruktory. Máme v podstatě dvě možnosti, jak to řešit. Každopádně do třídy umístíme statickou metodu initialize, která bude představovat náš statický konstruktor.

class MyClass
{
  /** variable with default value */
  private static $_defaultVariable;
 
  /** variable for an usage with an inheritance */
  private $_variable;
 
  public static initialize()
  {
    ........
    self::$_defaultVariable = .....
  }
}

Máme asi následující možnosti

  • Metodu initialize zavolat globálně v souboru se třídou, tím se její kód provede po natažení souboru s třídou.
  • Nějak to ošetřit autoloaderem tříd, po načtení třídy bude vždy autoloader volat metodu initialize, pokud ji třída obsahuje.

První případ by asi vypadal nějak takto

//file MyClass.php
 
class MyClass
{
  /** varible with default value */
  private static $_defaultVariable;
 
  /** variable for an usage with an inheritance */
  private $_variable;
 
  public static initialize()
  {
    ........
    self::$_defaultVariable = .....
  }
}
 
MyClass::initialize();

V druhém případě by modifikovaný autoloader mohl vypadat asi nějak takto

class MyLoader extends LibLoader
{
  public static function loadClass($className)
  {
    const INITIALIZER_NAME = "initialize";
 
    /*
     * creates $filename from $className...
     */
 
    include_once $filename;
 
    // a reference to the static constructor's operation
    $staticConstructorReference = array($className, self::INITIALIZER_NAME);
 
    // if the static constructor was declared properly, call this one
    if (is_callable($staticConstructorReference)) {
      call_user_func($staticConstructorReference);
    }
}

Těžko říci, co je lepší. Nejlepší by bylo, kdyby statické konstruktory byly už přímo v PHP, jednodušší je určitě první varianta, druhá je zase asi tak nějak čistší a systémovější. Ještě je tu ale třetí možnost, která se používá také docela často, možná i nejčastěji, je to vidět zejména v aplikacích založených na nějakých frameworcích, inicializace statických proměnných se se nechá na aplikaci a provede se většinou v bootstrapu aplikace, při natahování konfigurací apod.

  • Currently 105.789/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
current rating 3.53/5 (votes: 19)
viewpic 2395x
0 trackbacks - trackback url
5 comments - Add comment
1.   LLook - Aug 10, 2008 7:52 PM
Mě teda přijde čistší ten první způsob, je při něm jasné, že nahrání třídy jakýmkoli způsobem == initialize. Druhá možnost přidává závislost té třídy na svém autoloaderu, čímž dost omezuje její znovupoužití v jiných projektech.

Ale upřímně se mi nelíbí ani jedna z nastíněných možností.

Ještě je možné nepřistupovat k hodnotě přes vlastnost. Nadefinovat getter a inicializaci dát do něj:
class MyClass {
    private static function getDefaultVariable() {
        static $defaultVariable;
        if (!isset($defaultVariable)) {
            $defaultVariable = new Foo;
        }
        return $defaultVariable;
    }
}
Případně může getter vracet referenci, je-li to vhodné (většinou asi ne)...

První dvě možnosti vyžadovali poznámku v dokumentaci, tady je vše jasné ze samotného API. Výhodou oproti statickým konstruktorům a důvod pro použití i v jazycích, které je mají, může být lazy-loading. Objekt se inicializuje až v momentě, kdy je potřeba.
2.   v6ak - Aug 11, 2008 8:18 AM
Co takhle volat v každé metodě, kde je to potřeba, nějaké self::initOnce(), které by poprvé vše inicializovalo a pak nedělalo nic?
3.   koubel - Aug 11, 2008 10:20 PM
[1] ten první způsob není tak úplně odolný proti include/require, když to nebude include_once, tak se ta inicializace může volat vícekrát, ale to zase až tak vadit to nemusí

ten getter vypadá jako že by to šlo, díky

[2] to my nepříjde jako úplně šťastné, na to se bude zapomínat, ten statický getter bude lepší.
4.   LLook - Sep 6, 2008 4:17 PM
[3] Když to nebude include_once/require_once, tak především vznikne fatální chyba "Cannot redeclare class".
5.   koubel - Sep 7, 2008 4:49 PM
[4] no to je pravda

nakonec bych ten statický konstruktor v PHP stejně docela přivítal :-)
Add comment