Hoi,
ik wil graag een bijdrage doen dus bij deze een tutorial/techniek om singleton classen makkelijk te beheren
Inhoud
- Intro
- Directory structuur indelen
- De interface bepalen van onze class
- De Singleton class schrijven
- De CurrentUser class schrijven, de class die een Singleton is
- De User class schrijven
- Testen!
1. Intro
Je zult wel al moeten weten hoe je met objecten werkt.
Gelukkig is PHP makkelijk dus alsnog interessant/goed te doen voor beginners om dit door te lezen
Ik zal steeds de code die ik laat zien toelichten maar het commentaar en uitleg zal vooral in de code zelf staan!
2. Directory structuur indelen
De directory structuur komt er zo uit te zien:
- classes
---- CurrentUser.php
---- Singleton.php
---- User.php
-- interfaces
---- ISingleton.php
- index.php
Maak deze aan, laat de bestanden even leeg die gaan we zo invullen :-)
3. De interface bepalen van onze class
Een interface kun je zien als een omschrijving van de mogelijkheden van een class die deze interface implementeerd.
Neem bijv. een afstandbediening, iedere afstandsbediening heeft ongeveer deze interface (pseudo):
- Inschakelen
- Uitschakelen
- Volgende zender
- Vorige zender
- Volume harder
- Volume zachter
We weten nu precies wat voor mogelijkheden we hebben maar we hebben geen idee HOE de afstandsbediening dit uitvoert maar we weten wel welke functionaliteit we hebben, bij deze een stukje code om de interface van de Singleton class te bepalen:
PHP-code:
<?php
interface ISingleton
{
/**
* Retourneert instance van onze Singleton
*
* @return ISingleton
*/
static function getInstance();
}
Dus wat we nu hebben is dat de classen die de interface ISingleton implementeren de mogelijkheid hebben om een instantie op te vragen van de class.
Nu gaan we het makkelijk maken voor onszelf door een basis class te schrijven die dit voor ons doet en wij niks hoeven te doen, bijna niks dan.
Om even terug te komen op het afstandsbediening verhaal, wij gaan nu dus de Sony afstandsbediening maken en niet de bijv. Samsung afstandsbediening, volgende deel ga ik laten zien hoe Sony dit oplost.
3. De Singleton class schrijven
PHP-code:
<?php
// Singleton pattern class
// Deze is abstract, dit betekent dat deze class MOET ge-extend worden
// en kan nooit voorkomen dat deze code word uitgevoerd:
// $obj = new Singleton(), dit word een error dus.
abstract class Singleton
implements ISingleton
{
/**
* De ingeladen instanties
* hier zullen onze instanties inkomen en beheerd worden
*
* @var ArrayObject
*/
private static $instances;
/**
* Deze propertie houd bij of we zijn geinitialiseerd, zie: _staticInitialized()
*
* @var boolean
*/
private static $initialized;
/**
* Dit is de constructor, deze defineren we alleen om ervoor te
* zorgen dat de subclass nooit zal worden geinitialiseerd met new ...()
* Dit heet het Singleton design pattern
*/
protected function __construct()
{
}
/**
* Deze functie initialiseerd onszelf, dit kan je zien als een
* constructor alleen dan statisch
*
* @return void
*/
private static function _staticInitialize()
{
if( self::$initialized === false )
{
// initialiseer onze instances
self::$instances = new ArrayObject();
// we zijn geinitialiseerd, dit zorgt ervoor dat als deze
// functie meer dan 1x word aangeroepen dat de
// instances niet gereset word
self::$initialized = true;
}
}
/**
* Retourneert de instantie van de opgegeven class naam
*
* @param string $className
* @return ISingleton
*/
protected static function _getInstance($className)
{
// initialiseer onszelf indien nodig, deze check word in _staticInitialize() gedaan
self::_staticInitialize();
// check of de opgegeven class niet al eens is geinitialiseerd
if( self::$instances->offsetExists($className) === false )
{
// de class is nog niet eerder geinitialiseerd, initialiseer deze nu
self::$instances->offsetSet($className, new $className);
}
// vanaf dit punt weten we zeker dat de class is ingeladen
// tenzij er een error is opgetreden, deze check laat ik nu even achterwege
return self::$instances->offsetGet($className);
}
}
Deze class is nu onze Sony afstandsbediening, zoals je ziet implementeert deze de class ISingleton, normaal zou deze class nu verplicht zijn
om de functie getInstance() te implementeren maar omdat deze abstract is word ervoor gezorgt dat de subclass deze moet implementeren.
Dit is nodig omdat de class Singleton niet de naam kan opvragen van de class die Singleton extend (de subclass dus)
Dit is best jammer omdat je nu in iedere class getInstance() moet implementeren.
Verder is het nu vrij simpel, in het volgende hoofdstuk laat ik zien hoe we de CurrentUser gaan maken want we hebben ALTIJD maar 1 currentUser
je hebt nooit meer dan 1 actieve gebruiker, daarom is dit dus een Singleton.
Als het idee van het singleton patroon nog niet duidelijk is kun je het melden dan zal ik een betere uitleg proberen te geven mits je goed omschrijft wat je niet begrijpt.
4. De CurrentUser class schrijven, de class die een Singleton is
De CurrentUser is dus een Singleton en we hebben geen zin om elke keer de instance te beheren dus gaan we de functionaliteit van Singleton gebruiken
en uitbreiden met onze eigen class: CurrentUser
Dit is een vrij simpele class, ik zal zo toelichting geven over de getInstance() methode:
PHP-code:
<?php
// Houd de huidige gebruiker bij, je kunt via CurrentUser::getInstance()->user()->username() bijv de gebruikersnaam opvragen
// Nu is het bijv ook mogelijk om het korter te maken shortcut functies
// te maken in deze class maar dat moet je zelf weten
// hierdoor is het wel zo dat je wellicht dubbele code krijgt.
class CurrentUser
extends Singleton
// extenden zodat deze class alle functionaliteit krijgt van de class
// Singleton + functionaliteit die in deze class zelf word geschreven
{
/**
* De gebruiker instantie
*
* @var User
*/
private $user;
protected function __construct()
{
// laat de Singleton ook zichzelf "constructen"
parent::__construct();
// initialiseer de Gast als standaard gebruiker
$user = new User();
$user->hasIdentity(false);
$user->username('Gast');
// gast instellen
$this->user = $user;
}
/**
* Retourneert de instantie van deze class
* Herinner de functie uit de ISingleton interface nog?
* Hier word die nu geimplementeerd
* Dit laat de parent, de parent is in dit geval de class Singleton en
* laat die een instantie van deze class ophalen of aanmaken.
* Omdat we nu in een "static function" zitten is er dus nog GEEN
* instantie aangemaakt van deze class!
*
* Voor de gevorderde phpers, misschien hebben jullie hier een
* antwoord op:
* Het is jammer dat deze functie hierin moet maar vanuit de
* Singleton class kan je in PHP op 1 of andere manier niet de
* classname opvragen van de subclass
* In die geval dus CurrentUser maar Singleton kan daar niet
* bijkomen, vandaar deze "hack"
*
* @return CurrentUser
*/
public static function getInstance()
{
return parent::_getInstance(__CLASS__);
// __CLASS__ retourneert de classnaam van zichzelf
}
/**
* Deze functie is een setter/getter in 1, als er een User word
* meegegeven in de parameters dan zal deze worden gezet, anders niet
* bovendien word er ALTIJD een User object teruggegeven, een
* Guest is tenslotte ook een user maar zonder identiteit.
*
* @param User $user
* @return User
*/
public function user(User $user=null)
{
if( $user !== null )
$this->user = $user;
return $this->user;
}
}
Check commentaar voor uitleg.
5. De User class schrijven
Dit is een class die gegevens van een gebruiker bevat zoals de username en identiteit check (tot nu toe, later zou je dit natuurlijk kunnen uitbreiden met een email, password, voornaam, achternaam noem het maar op)
Hier zeg ik niet veel over, dit is een zeer simpele class, lijkt me duidelijk zat, alsnog wel overal commentaar toegevoegd.
PHP-code:
<?php
// deze class is nu heel erg simpel maar deze kan natuurlijk zo uitgebreid worden als je zelf wil!
class User
{
/**
* De gebruikersnaam
*
* @var string
*/
private $username;
/**
* Als deze variable op true staat betekent dit dat de gebruiker is ingelogd
*
* @var boolean
*/
private $hasIdentity;
// Initialisatie
public function __construct()
{
$this->hasIdentity = false;
}
/**
* Sets/gets de gebruikersnaam
*
* @param string $value
* @return string
*/
public function username($value=null)
{
if( $value !== null )
$this->username = $value;
return $this->username;
}
/**
* Retourneert of de gebruiker een identiteit heeft, zoja, dan is
* deze als ingelogd "gaflagged"
*
* @param boolean $value
* @return boolean
*/
public function hasIdentity($value=null)
{
if( $value !== null )
$this->hasIdentity = (boolean)$value;
// (bool) "cast" de waarde ($value) om naar een boolean zo weten we
// altijd zeker dat we een boolean terug krijgen
return $this->hasIdentity;
}
}
Lijkt me duidelijk. En dan nu eindelijk, testen! ^^
6. Testen!
Plaats in je index.php:
PHP-code:
<?php
// start de sessie
session_start();
// zet error reporting aan
error_reporting(E_ALL);
// include onze interfaces
require_once 'interfaces/ISingleton.php';
// include onze classen
require_once 'classes/Singleton.php';
require_once 'classes/User.php';
require_once 'classes/CurrentUser.php';
// Probeer onze class uit!
$currentUser = CurrentUser::getInstance()->user();
// We hebben nu nog geen gebruiker toegekend die de currentUser zou
// zijn dus als we nu de username gaan uitprinten krijgen we Gast te zien:
echo 'currentUser[username]: ' . $currentUser->username() . '<br />' . PHP_EOL;
// Dit werkt, nu gaan we een user aanmaken, deze zou normaal
// gesproken uit de sessie komen, dan checken in database,
// gegevens ophalen en dan de user invullen op basis van database
// gegevens, nu doen we het even hard coded, database
// tutorials komen later wel ^^
$user = new User();
$user->hasIdentity(true);
$user->username('X5');
// stel deze gebruiker in als ingelogde gebruiker
$currentUser = CurrentUser::getInstance()->user($user);
// laten we nogmaals de gebruikersnaam weergeven:
echo 'currentUser[username]: ' . $currentUser->username();
// voila!
// Mijn output was:
/*
currentUser[username]: Gast
currentUser[username]: X5
*/
// een testje om te laten zien dat CurrentUser maar 1x aangemaakt mag worden:
// uncomment de volgende regel uit om het te zien, dan zul je de fout
// zien en kan je deze later terugherkennen als je deze techniek toeapast
//$user = new CurrentUser();
Er staat veel commentaar in, indien niet duidelijk hoor ik het graag.
Alle classnamen komen overeen met hun bestandsnaam!
Greetings,
X5.