Mirin webspace

Nejbohatší život má ten, kdo žije s minimem nároků

10. 8. 2008 - Komentáře (5) PHP

Statický konstruktor

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.


Komentáře (5)

  1. LLook - 10. 8. 2008 19:52

    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 - 11. 8. 2008 08:18

    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 - 11. 8. 2008 22:20

    [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 - 6. 9. 2008 16:17

    [3] Když to nebude include_once/require_once, tak především vznikne fatální chyba "Cannot redeclare class".

  5. koubel - 7. 9. 2008 16:49

    [4] no to je pravda

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

Komentáře jsou uzavřeny.