Facebook
Twitter
Google+
Kommentare
6
Willkommen bei "the web hates me". Mittlerweile hat unser Team ein tolles neues Monitoringtool für Agenturen gelauncht. Unter dem Namen koality.io haben wir einen Service geschaffen, der er Agenturen ermöglicht mit sehr geringen Kosten alle Ihre Webseiten zu überwachen.

Abstract Singleton

Ich weiß, ich bin heute ziemlich spät dran, das liegt aber nicht daran, dass ich keine Lust hatte, aber mein Tag war wirklich anstrengend. Deswegen gibt es heute auch nur einen kurzen Beitrag von mir.
Ich habe ja schon vor ein paar Tagen über das Singleton Pattern geredet. Dank PHP 5.3 ist es jetzt kein Problem mehr schon alles für dieses Entwurfsmuster in einer abstrakten Elternklasse zu implementieren.

abstract class aSingleton
{
   private static $instance;

   public static function getInstance( )
   {
      $className = get_called_class( );
      if ( ! isset(self::$instance ) )
      {
         self::$instance = new $className( );
      }
      return self::$instance;
   }

   protected function __construct( )
   {
   }

   final private function __clone( )
   {
   }
}

Prinzipiell habe ich ja bereits bei meinem Artikel über die Singleton alles erzählt, was nötig ist, deswegen werde ich jetzt nur noch anmerken, wie man das ganze verwenden sollte.

Class Database extends aSingleton
{
}

$db = Database::getInstance( );

Vielleicht kann man ja noch als Schlusswort hinzufügen, dass es nur funktioniert solange man einen einheitlichen Konstruktor für alle Singletons benutzt, da man in der abstrakten Klasse nicht unterscheiden kann.

Sorry wenn dieser Artikel ein wenig hektisch wird, aber man hat leider nicht immer genug Zeit.

Über den Autor

Nils Langner

Nils Langner ist der Gründer von "the web hates me" und auch der Hauptautor. Im wahren Leben leitet er das Qualitätsmanagementteam im Gruner+Jahr-Digitalbereich und ist somit für Seiten wie stern.de, eltern.de und gala.de aus Qualitätssicht verantwortlich. Nils schreibt seit den Anfängen von phphatesme, welches er ebenfalls gegründet hat, nicht nur für diverse Blogs, sondern auch für Fachmagazine, wie das PHP Magazin, die t3n, die c't oder die iX. Nebenbei ist er noch ein gern gesehener Sprecher auf Konferenzen. Herr Langner schreibt die Texte über sich gerne in der dritten Form.
Kommentare

6 Comments

  1. Apropos nicht genug Zeit: das müsste __clone() sein, nicht clone(). Und die Eigenschaft $instance sollte als static deklariert sein. 🙂

    Ich kann zwar den Hintergrund verstehen, nämlich dass man nicht immer wieder ein Singleton neu implementieren möchte, finden den Ansatz aber trotzdem nicht gut. Zum einen denke ich ist es wenig sinnvoll, etwas vom Typ Singleton zu haben (wobei sollte das helfen?), zum anderen bekommt man damit eine Hierarchie in alle Singletons, die eine Abhängigkeit darstellt welche so nicht gegeben ist.

    IMHO wäre es sinnvoller zu warten bis Traits verfügbar sind, und das dann darüber zu lösen.

    Reply
  2. Morgen Frank, da muss ich dir absolut recht geben, bei allen deinen Punkten. Die beiden Fehler im Code habe ich behoben, dass ich nicht immer ein Freund des Singleton Musters bin, habe ich ja auch schon geschrieben.
    Die Traits Idee werde ich auch jeden Fall noch mal aufgreifen, finde ich ziemlich „sexy“.

    Reply
  3. Hallo Leute!
    Ich beobachte diesen Blog schon längere Zeit und ich muss sagen hier sitzen wirklich fähige Autoren dahinter!

    Nun zu meinem Kommentar:
    Ich entwickle schon seit anbeginn von PHP5.3 ein recht großes Framework für PHP5.3

    Meine Singleton Implementation sieht ein wenig anders aus:

    /**
     * Abstract Singleton Class
     */
    abstract class Singleton
    {
    	/**
    	 * The name of the class
    	 *
    	 * @var string classname
    	 */
    	public static $classname = __CLASS__;
    	
    	/**
    	 * The instance of the class
    	 *
    	 * @var Singleton
    	 */
    	protected static $instance;
    	
    	/**
    	 * Returns the instance
    	 *
    	 * @return Singleton
    	 */
    	public static function getInstance()
    	{
    		if(!is_null(static::$instance))
    			static::$instance = new static::$classname();
    			
    		return static::$instance;
    	}
    	
    	/**
    	 * Constructor. Singleton.
    	 */
    	protected function __construct()
    	{
    		
    	}
    	
    	
    	/**
    	 * Cloning is not allowed
    	 */
    	final private function __clone()
    	{
    		
    	}
    }
    
    /**
     * A Counter
     */
    class Counter extends Singleton  
    {
    	/**
    	 * The name of the class
    	 *
    	 * @var string
    	 */
    	public static $classname = __CLASS__;
    	
    	/**
    	 * The instance of the class
    	 *
    	 * @var Singleton
    	 */
    	protected static $instance;
    	
    	/**
    	 * Constructor. Singleton.
    	 */
    	protected function __construct()
    	{
    		//Do some stuff here
    	}
    	
    	/**
    	 * Returns the count
    	 *
    	 * @return int
    	 */
    	public function count()
    	{
    		//Or Here
    	}
    }

    Nutzt halt LSB.
    Das schöne is, damit kann man „schöne“ Enums in PHP realisieren.
    Nachteil ist natürlich das man den Klassennamen und die Instanzvariable in jeder abgeleiteten Klasse hinterlegen muss.

    Bei mir ist das mit dem Klassennamen nicht so das Problem, weil diese Variable bei mir ohnehin in jeder Klasse vorhanden ist.

    Grüße aus Österreich
    Manuel

    Reply
  4. @manuel: Erstmal Danke für das Kompliment. Kannst du mal ein Beispiel zu dem ENUM Vorteil bringen? Das ist mir irgendwie noch nicht so ganz klar. Denn ohne das Bsp. finde ich unsere Implementierung schöner, da wir keine protected Variable mitschleppen müssen.

    Reply
  5. ja, da hast du vollkommen recht, nur mit singletons muss/soll man ohnehin sparsam umgehen, nur beim enum gehts nicht ohne.

    Via Reflection ist sogar ein iterierbarer Enum machbar (Delphi like)

    namespace HazAClass\type;
    
    use HazAClass\type\exception\IteratorMethodWasTriedToUsedAsEnumValue;
    
    /**
     * Base-Class for Typesave Enum
     */
    abstract class Enum implements \IteratorAggregate, \Countable
    {
    	/**
    	 * Der Name der Klasse
    	 *
    	 * @var string classname
    	 */
    	public static $classname = __CLASS__;
    
    	/**
    	 * Value of enum
    	 *
    	 * @var string
    	 */
    	private $value;
    
    	/**
    	 * The Methods/Values in the Enum
    	 *
    	 * @var array[Enum]
    	 */
    	private static $methods;
    
    	/**
    	 * Sets value of enum
    	 *
    	 * @param string $value
    	 */
    	final protected function __construct($value)
    	{
    		$this->value = $value;
    	}
    
    	/**
    	 * Returns an 'null'-enum used to iterate a enum.
    	 *
    	 * Note: if you try to use this enum-value as an 'real' enum
    	 * an exception is thrown in the getValue or __toString method!
    	 *
    	 * @return Enum
    	 */
    	final public static function iterator()
    	{
    		return new static::$classname(null);
    	}
    
    
    	/**
    	 * Returns the current value of the enum
    	 *
    	 * @see iterator
    	 * @throws IteratorMethodWasTriedToUsedAsEnumValue
    	 *
    	 * @return string
    	 */
    	final public function getValue()
    	{
    		if(is_null($this->value))
    			throw new IteratorMethodWasTriedToUsedAsEnumValue('The iterator-method can only be used to iterate, not as an value');
    		return $this->value;
    	}
    
    	/**
    	 * Casts Enum to String
    	 *
    	 * @return string
    	 */
    	final public function __toString()
    	{
    		if(is_null($this->getValue()))
    			return '';
    		return $this->getValue();
    	}
    
    	/**
    	 * @see Countable::count()
    	 *
    	 */
    	public function count()
    	{
    		return count(self::getMethods());
    	}
    
    	/**
    	 * Returns the values (methods) in the enum
    	 *
    	 * @return array[methods]
    	 */
    	private static function getMethods()
    	{
    		if(is_null(self::$methods))
    		{
    			$ref = new \ReflectionClass(static::$classname);
    
    			foreach($ref->getMethods(\ReflectionMethod::IS_PUBLIC) as $method)
    			{
    				if($method->isStatic())
    				{
    					$name = $method->getName();
    					if($name != 'iterator')
    					{
    						$classname = static::$classname;
    						self::$methods[$name] = $classname::$name();
    					}
    				}
    			}
    		}
    		return self::$methods;
    	}
    
    	/**
    	 * @see IteratorAggregate::getIterator()
    	 */
    	public function getIterator()
    	{
    		return new \ArrayIterator(self::getMethods());
    	}
    
    }

    das is halt schon a recht komplexe implementierung, aber dann kann man folgendes machen:

    
    namespace HazAClass\output;
    
    use HazAClass\type\Enum;
    
    /**
     * Classdescription
     */
    class OutputTypes extends Enum
    {
    	/**
    	 * The Name of the class
    	 *
    	 * @var string $classname
    	 */
    	public static $classname = __CLASS__;
    
    	/**
    	 * xhtml
    	 *
    	 * @return OutputTypes
    	 */
    	public static function xhtml()
    	{
    		return new self::$classname('xhtml');
    	}
    
    	/**
    	 * xml
    	 *
    	 * @return OutputTypes
    	 */
    	public static function xml()
    	{
    		return new self::$classname('xml');
    	}
    }

    und dann:

    foreach (OutputTypes::getIterator() as $outputType)
    	echo $outputType;

    Das schöne ist, das die wirklich typensicher sind

    also

    public function render(OutputTypes $type)
    {
    //Do Something here
    }

    Hoffe das ist klar verständlich soweit, sonst einfach rühren ^^

    Reply
  6. Ich seh grad das is was aus nem alten commit hochgekommen..

    Die iterator methode kann man streichen, dann spart man sich die abfrage im getMethods, weil im getIterator eh kein $this aufgerufen wird (da wären wir wieder bei einem älteren eintrag von euch ^^, in dem fall sogar sinnvoll einsetzbar)

    es ginge dann sogar:
    foreach (OutputTypes::xhtml() as $outputType)
    echo $outputType;

    aber das ist schwer zu lesen, und irreführend.

    Reply

Leave a Comment.

Link erfolgreich vorgeschlagen.

Vielen Dank, dass du einen Link vorgeschlagen hast. Wir werden ihn sobald wie möglich prüfen. Schließen