Ik ben een Request class aan het maken. Via deze class kan ik bijv. het domein van de opgevraagde URL opvragen, of het subdomein, en of het een beveiligde (https) verbinding betreft. De gegevens haal ik op uit de $_SERVER array. Nu had ik gewoon allemaal public funtcions gemaakt. Echter... ik realiseer me ineens dat gedurende 1 request de $_SERVER array altijd hetzelfde is. Het zou dan raar zijn als ik telkens als ik de Request class nodig heb "new Request()" zou doen.
Nu vraag ik me af wat volgens jullie de beste oplossing is. Ik zou een singleton kunnen maken, zodat je niet "$request = new Request()" doet, maar "$request = Request::getInstance()". Wat ik ook kan doen is iedere functie in de Request class static maken, zodat ik het domein bijv. als volgt opvraag: $domain = Request::getDomain();
return Return het geen hierachter staat
new maak een nieuw object aan
static static verwijst naar het object waar we inzitten, Request in dit
geval. Maak dus een nieuw request object aan
( het begin van de parameters
$_SERVER, eerste parameter is de $_SERVER super global
$_POST 2e parameter is de $_POST super global
) sluit de parameters
; sluit de regel
1) Wat zit er nu in de variabele $request?
Hetgeen geretourneerd wordt door Request::createFromGlobals(), een nieuwe request object dus, opgemaakt uit de super globals.
2) Nu wil ik uit de server array de variabele HTTP_HOST hebben. Hoe doe ik dit dan?
<?php
$request = Request::createFromGlobals();
echo ($request->getServer())['HTTP_HOST'];
?>
Of als je een ParameterBag gebruikt, wat ik mooier vind:
<?php
// ...
echo $request->server->get('HTTP_HOST');
?>
1) Wat is nu precies het verschil van deze manier met "mijn" manier? En dan bedoel ik wat het verschil is met $_SERVER meegeven en $_SERVER rechtstreeks gebruiken. Is het op jouw manier zo dat de class property $server wel de inhoud van $_SERVER bevat, maar niet daadwerkelijk de $_SERVER array is? Je spreekt dus niet rechtstreeks de $_SERVER array aan? Klopt dit zoals ik het nu uitleg?
2) ($request->getServer())['HTTP_HOST'] Is dit PHP 5.4 schrijfwijze, waarbij je de key 'HTTP_HOST' ui de array opvraagt? Werkt het ook zonder haken, dus zo $request->getServer()['HTTP_HOST']. Ik zou het overigens dan liever zo doen: $request->getServer('HTTP_HOST'). Of gewoon $request->getHttpHost();
3) Dat ziet er mooi uit, maar hoe krijg je dat voor elkaar? ParameterBag zegt me (nog) niks.
vraag 2...nee dat is een algemene schrijfwijze..en zonder die haakjes gaat waarschijnlijk niet, omdat hij eerst de variabele moet returnen en dan pas de value van die key uit lezen..
vraag 3. heel simpel...impv een array retourneer je een object waar de $_SERVER variabele ingestopt is die heeft dan getters en setters.
1) Dat klopt, $server is een copy van $_SERVER. Dit betekend dat je $server kan aanpassen zonder $_SERVER aan te passen en dat de klasse dus niks weet van $_SERVER. Dit kan handig zijn in tests, waar je geen toegang hebt tot $_SERVER globals en dus gewoon een array met wat keys meegeeft.
2) Dat is inderdaad de PHP5.4 schrijfwijze waarbij je meteen een key van de array kunt ophalen zonder hem eerst in een variabele op te slaan. Dit werkt niet zonder haken.
$request->getServer('HTTP_HOST') ziet er leuk uit, maar ik zou dan de methodnaam veranderen naar getServerParameter en dan ga je eigenlijk al teveel weten in de Request klasse over die server parameter. De laatste methode ($request->getHttpHost()) is natuurlijk helemaal niet goed omdat HttpHost helemaal geen eigenschap (property) van Request is.
3) Een ParameterBag is gewoon een klasse. Voorbeeldje:
<?php
/**
* @param array $parameters
*/
public function __construct($parameters = array())
{
$this->setParameters($parameters);
}
public function set($id, $value)
{
if ($this->isFroozen()) {
throw new \OverflowException('You cannot change a froozen parameterbag');
}
$this->parameters[$id] = $value;
}
public function get($id)
{
if (!$this->has($id)) {
throw new \OutOfBoundsException(sprintf('The parameter "%s" is not found', $id));
}
return $this->parameters[$id];
}
public function has($id)
{
return array_key_exists($id, $this->getParameters());
}
/**
* @return array
*/
public function getParameters()
{
return $this->parameters;
}
public function freeze()
{
if ($this->isFroozen()) {
throw new \BadMethodCallException('The parameterbag is already froozen');
}
$this->froozen = true;
}
public function isFroozen()
{
return $this->froozen;
}
/**
* {@inheritDoc}
*/
public function getIterator()
{
return new \ArrayIterator($this->getParameters());
}
/**
* {@inheritDoc}
*/
public function count()
{
return count($this->getParameters());
}
private function setParameters(array $parameters)
{
$this->parameters = $parameters;
}
}
?>
De request klasse ziet er dan zo uit:
<?php
class Request
{
/**
* @var ParameterBag
*/
public $server;
/**
* @param array $server
*/
public function __construct($server = array(), ...)
{
$this->setServer($server);
}
// ...
private function setServer(array $server)
{
$this->server = $s = new ParameterBag($server);
$s->freeze();
}
}
?>
In het gebruik:
<?php
$request = new Request::createFromGlobals();
Ik wil het een beetje simpel houden hè Wouter :)
Nou, weer een paar vragen dan maar...
1) Eerder schreef je dit "return new static($_SERVER);". Is dat exact hetzelfde als dit "return new Request($_SERVER);"
2) In mijn huidige request class heb ik een functie die mij bijvoorbeeld het subdomein teruggeeft: $request->getSubdomain();
Hoe zou je dat dan in jouw voorbeeld implementeren? Wel gewoon in de request class een functie getSubdomain maken, maar dan in die functie gewoon de http_host opvragen via via $this->server->get('HTTP_HOST')?
3) Ik zie in het voorbeeld wat jij geeft allerlei typen Exceptions staan. Waarom gebruik je niet gewoon 1 (algemeen) type?
4) Het voorbeeld van die ParameterBag (waarom heet het zo?) komt dat uit Symfony, of is het iets wat je zelf hebt gemaakt?
5) Dit wordt een leuke... probeer mij nou eens 100% te overtuigen waarom ik van de Request class geen Singleton ga maken (het argument van Unit testing moet je even achterwege laten). Hoe ik het zie... stel ik heb straks in mijn framework op meerdere plekken het request object nodig. De gegevens uit $_SERVER, $_POST etc. zullen tijden een request niet veranderen. Stel ik heb het request op 10 plekken nodig. Als het geen Singleton is, dan moet ik 10x een object aanmaken, parameters instellen enz. Als ik een Singleton gebruik, roep ik telkens hetzelfde object aan. Ik verbruik geen extra geheugen en behaal performance winst omdat niet telkens alle parameters opnieuw hoeven te worden geset. Mij lijkt het dus (in dit specifieke geval!!!) een hele hoop voordelen bieden.
Is dit dan niet simpel (behalve misschien technieken die je onbekend voorkomen), een klasse van 90 regel en methoden van 1 tot 5 regels is nog niet zo moeilijk toch?
1) Ja, maar new static is wat dynamischer zodat als je de Request klasse gaat extenden in bijv. een JsonRequest klasse hij `return new JsonRequest($_SERVER)` doet.
2) Nee, een subdomein is geen eigenschap van de request, maar eentje van de SERVER headers. Wat je kan doen is deze als key toevoegen in de SERVER array (die in je klasse staat, niet de $_SERVER array)
3) Je moet zo precies mogelijk zijn met exceptions. Sommige wil je direct opvangen, andere laat je opborrelen tot het einde en nog andere vang je halverwege op. Tevens helpt dit je debug message, je kan dan precies zien wat er fout is gegaan aan de exception klasse en ook waar ong. (door bijv. exceptions in namespaces te zetten)
4) Het idee van ParameterBag komt uit de Symfony HttpFoundation Component (het geen waar Request en zijn factory ook vandaan komen), het idee van het bevriezen van een Registery komt uit de Symfony DependencyInjection Component. De code zelf heb ik zojuist gemaakt
5) Correct, behalve 1 heeeele grote fout en dat is dat je niet met singletons moet gaan werken om een klasse in een andere klasse te krijgen. Je moet dan werken met Dependency Injection. Je maakt 1 keer de request klasse aan, slaat die ergens op en gebruikt die telkens weer. Als we eens de Pimple Container van Fabien erbij pakken wordt het:
<?php
$container = new Pimple();
$container['foo.class'] = 'FooClass';
$container['foo'] = function ($c) {
return new $c['foo.class']($c['request']); // voer het eerder aangemaakte Request object in
};
$container['bar.class'] = 'BarClass';
$container['bar'] = function ($c) {
return new $c['bar.class']($c['request']); // we maken hem niet nogmaals aan, maar gebruiken de eerder aangemaakte Request
};
?>
Pfff... oké, dependency injection dus. Moet ik me daar weer even in gaan verdiepen. Het valt allemaal niet mee... thanks voor de voorbeelden. Ik ga er mee aan de slag. Eens even goed over nadenken hoe ik dit ga doen. Heb voorlopig weer wat te doen ;)
Toevoeging op 04/01/2013 16:48:19:
P.s. Is het dan wel handig om de constructor in de request class private te maken, zodat je altijd "verplicht" bent om de createfromglobals functie te gebruiken?
Is het dan wel handig om de constructor in de request class private te maken, zodat je altijd "verplicht" bent om de createfromglobals functie te gebruiken?
Nee, want ik wil helemaal niet altijd createFromGlobals gebruiken.