MVC Routing
Ik ben bezig met het ontwikkelen van een eigen MVC. Via htaccess stuur ik alles door naar mijn index. Mijn index bepaalt vervolgens welke controller moet worden aangeroepen. Stel mijn url is /nieuws/nieuwsitem. In dit geval moet de NewsitemController worden aangeroepen om het nieuwsitem te tonen. Stel mijn URL is /over-ons. Dan moet de ContentController worden aangeroepen en een contentpagina getoond worden. Maar nu mijn vraag.. Hoe weet index welke controller moet worden aangeroepen?
procedureel:
In plaats van de routes direct in een array te zetten kun je ook Yaml, XML of JSON gebruiken. Door bijv. een array (van objecten) aan te maken met routes. Programmeer je in OOP of procedureel?
Ik programmeer in OOP. Heb je daar ook een voorbeeldje van?
Ik ben zelf ook nog altijd bezig met MVC te leren hoor.. Misschien heb je hier wat aan. Zou je niet gewoon controllers maken van je pagina's ? En deze dezelfde naam geven als de URL[0] ?
Ik gebruik nu het volgende, maar ik ben van mening dat het nog stukke beter kan..
Verder laat je de applicatie nu allerlei models en controllers langslopen, maar kun je dat ook overdragen aan een front controller.
Schematisch krijg je dan zoiets:
Je steekt daarnaast steeds een model in een controller, bijvoorbeeld:
Als elke FooController in jouw architectuur echter een bijbehorend FooModel heeft, kun je ook alleen de controller aanroepen. In MVC wil je liever alleen controllers met andere controllers laten communiceren. Jij hebt echter nu naast die bovenwereld nog een schaduwwereld: je moet parallel steeds alle models instantiëren voordat je überhaupt een controller aan het werk kunt zetten.
Uit: HMVC: The layered pattern for developing strong client tiers
Verder is dat ook niet efficiënt. De controller bepaalt (bijvoorbeeld op basis van het request) of het model eigenlijk wel nodig is. Sommige models zijn slechts nodig als je een UPDATE of een INSERT wilt uitvoeren. Die models hoef je dus lang niet altijd te laden en te instantiëren. Het kan mooier door van de route ook een object te maken. En eigenlijk ook van het request. Alles is een object.
Hoe zou dat beter kunnen dan?
Alleen de controller weet waar Abraham de mosterd haalt. Dat kan bijvoorbeeld data uit een database zijn, maar diezelfde data kunnen ook uit een bestand komen, uit een cache, uit een cURL-request, enzovoort. Dat moet allemaal voorbij gaan aan andere objecten. Die spreken de controller gewoon aan en krijgen het antwoord dat ze verwachten.
Je hebt die regel nu omgekeerd met een "voorschrift": je instantieert eerst een model en schrijft dat vervolgens voor aan de controller door het te injecteren in de controller. Vuistregel: alleen de controller communiceert met het eigen model.
Hoe zou ik dat kunnen verbeteren dan?
Misschien een setModel() methode?
Een view kán een controller aanroepen. Die doen dan plaatselijk in hun beperkte context hun eigen ding. Niets mis mee.
Voorbeeld: de view is een widget die een beoordeling in sterren toont. De view roept daarvoor een controller aan die de beoordeling "ergens" uit een model haalt. Maakt niet uit waar: het kan de eigen database zijn, maar ook een cURL-request naar KiyOh of iets dergelijks.
De complicatie is dat je dergelijke dingen moet samensmeden tot één fraaie webpagina. En dan komt het HMVC-model van pas, met meerdere hiërarchisch gestapelde MVC-componenten. Díé componenten moet je koppelen via hun controllers, niet via hun models. Frank, het een sluit het ander niet uit.
Kun je daar een voorbeeldje van geven?
Misschien zoiets?
controller A wordt aangeroepen en die roept zijn eigen VIEW aan. Ik neem aan dat de VIEW niet direct output genereert maar eerder een response object vult en dit object dan weer teruggeeft aan de controller?
Daarna roept controller A controller B aan? Deze roept ook weer zijn VIEW aan en deze geeft ook een response object terug? Dan zitten we dus met twee response objecten?? Oké dus in een HMVC is het wel mogelijk om vanuit de view een andere controller in beweging te brengen. Dat je de controllers met elkaar koppelt dat klinkt wel logisch. Maar nu:
Yep, goed gezien, vooral dat zit in de weg.
Daarvoor hebben we dan een registry met één request en één response.
Daarom hebben we ook de neiging om het request/response-systeem te modelleren als een besturingssysteem.
1. Er komt één request binnen. Helder: dít is ons HTTP-verzoek, hier moeten we een antwoord op vinden.
Van dat request is er maar één, dus we hebben een uniek instance die we centraal kunnen registreren.
2. Van die request moeten onderdelen worden gedelegeerd aan specifieke controllers.
Hier komt het op creativiteit aan. Alleen de webdeveloper weet wat het systeem met specifieke requests moet doen.
3. Er komt één response uit.
We kunnen HTTP-output genereren of een HTTP-statuscode, maar veel alternatieven biedt HTTP ons niet.
De request wordt beantwoord met een response óf een code voor een ongeldig request. >> Dan zitten we dus met twee response objecten??
@Roy: In het systeem dat Ward beschrijft gaat het inderdaad om die manier. Ward geeft dat ook duidelijk aan:
Het enige verschil tussen jouw voorbeeld en die van Ward is dat Ward het Model aan de constructor meegeeft en jij daar een aparte method voor aanroept.
In andere MVC's wordt het model in de controller bepaald. Je krijgt dan zoiets:
dispatcher:
Controller:
Bedankt voor je heldere uitleg Ward. (Ben nog wel even aan piekeren hoe de developer dan zorgt dat dat ene response object dan bij de tweede en derde layer op de juiste manier uitgebreid wordt).
Het probleem waar ik een beetje tegen aanloop is welke method moet ik nu aanroepen in het Model van Ward.
Je krijgt namelijk het hele object geïnjecteerd in je Controller maar in dat object kunnen twintig verschillende methods zitten... Het is een keuze denk ik. Ik zou het doen maar dat is omdat ik het op die manier gewend ben. Ward mag zijn methode nog eens onderbouwen ;-).
Bij variant A moet je overal steeds in de weer met twee objecten: model en controller. Bij variant B heb je maar één API: je spreekt de controller aan en die zoekt verder zelf maar uit of er een model nodig is, hoe dat model er uitziet en hoe met het model wordt gecommuniceerd.
Bij variant A kun je makkelijker verschillende combinaties van models en controllers maken, wat met name bij frameworks handig kan zijn. Je zou in het voorbeeld UserModel door AdminModel kunnen vervangen. Bij variant B is die logica verborgen, wat zowel een voordeel als een nadeel kan zijn.
Variant B leent zich ook wat beter voor het inzetten en veranderen van design patterns: een blanco new User() kun je op verschillende manieren bouwen, terwijl er bij een new User(\UserModel $model) al veel meer is voorgeschreven.
Het is dus inderdaad een keuze. Laten we ze dan eens vergelijken:
Ik zal ook even een spreekwoordelijke duit in het spreekwoordelijke zakje doen.
Wanneer geven we waardes of een object mee aan de constructor? Op het moment dat een class kan werken met dynamische data. Zo kan een database class via de constructor verschillende "verbindingsobjecten" binnenkrijgen om zodoende met verschillende databases contact te kunnen maken, en een e-mailer class kan bijvoorbeeld verschillende e-mailadressen ontvangen om naar verschillende personen te kunnen mailen.
Praten we over een model, dan ligt de situatie echter wat anders. Mijns insziens is een model niets meer dan een verlengstuk van een controller. De code die in het model staat, zouden we ook rechtstreeks in de controller kunnen plaatsen, maar dat doen we niet omdat we alles netjes gescheiden willen houden. En ... mochten we ooit een ander model gaan gebruiken, dan hoeven we niet alle code in de class zelf aan te passen.
Het klinkt natuurlijk leuk om het model via de constructor mee te geven, want dan hoeven we de class nooit aan te passen als we een ander model willen gebruiken. Maar ... is het logisch? Ik vind van niet. Een model is namelijk afgestemd op de controller. De controller en het model zijn onlosmakelijk met elkaar verbonden. Ze horen bij elkaar. Ze zijn als Bert en Ernie, of als Bassie en Adriaan ... maar net waar je zelf de voorkeur aan geeft. Het is dan ook vreemd om van buitenaf te gaan bepalen welk model er nodig is. Dat hoort niet. Als "buitenstaander" praat je enkel met de controller, en die controller bepaalt zelf welk model er nodig is. Het is de controller die dit bepaalt, en niet de "buitenstaander".
Op het moment dat je een AdminModel wilt gebruiken in een UserController zoals Ward hierboven aangeeft, zou het raar zijn om als "buitenstaander" zelf een model te kiezen en dat mee te geven aan de constructor van de UserController. Wie zegt dat de UserController wel overweg kan met dat door de "buitenstaander" gekozen model? Logischer zou het dan zijn om in de constructor van de UserController een parameter in te bouwen:
Op deze manier houd je de beslissingsbevoegdheid bij de controller zelf.
Bottomline: als "buitenstaander" praat je enkel met de controller, en beslis je niet welk model de controller moet gebruiken. Deze bevoegdheid ligt bij de controller zelf.
Amen ... :) Heren, interessante discussie.
procedureel:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
$routes = array(
array(
'route' => 'nieuws',
'controller' => 'Newsitem',
'action' => 'index', // welke method van de controller-class moet aangeroepen worden?
'method' => 'GET' // keuze uit GET en/of POST
),
array(
'route' => 'over-ons',
'controller' => 'Content',
'action' => 'index',
'method' => 'GET'
),
};
?>
$routes = array(
array(
'route' => 'nieuws',
'controller' => 'Newsitem',
'action' => 'index', // welke method van de controller-class moet aangeroepen worden?
'method' => 'GET' // keuze uit GET en/of POST
),
array(
'route' => 'over-ons',
'controller' => 'Content',
'action' => 'index',
'method' => 'GET'
),
};
?>
In plaats van de routes direct in een array te zetten kun je ook Yaml, XML of JSON gebruiken.
Frank Nietbelangrijk op 27/06/2014 22:02:24:
Programmeer je in OOP of procedureel?
Ik programmeer in OOP. Heb je daar ook een voorbeeldje van?
Code (php)
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
if(file_exists('controllers/' . $route[0] . '.php')) {
require('controllers/' . $route[0] . '.php');
//Controle of de Controller-class bestaat, anders doorsturen naar een 404 ???
if(class_exists($route[0]))
$this->controller = new $route[0];
else
header('location: 404.php');
} else {
//Controller file bestaat niet, doorsturen naar een 404 ?
header('location: 404.php');
}
require('controllers/' . $route[0] . '.php');
//Controle of de Controller-class bestaat, anders doorsturen naar een 404 ???
if(class_exists($route[0]))
$this->controller = new $route[0];
else
header('location: 404.php');
} else {
//Controller file bestaat niet, doorsturen naar een 404 ?
header('location: 404.php');
}
Ik ben zelf ook nog altijd bezig met MVC te leren hoor.. Misschien heb je hier wat aan.
Gewijzigd op 22/11/2014 21:13:34 door Dennis WhoCares
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
include "includes/config.php";
$route = explode("/", trim(strtolower($_SERVER["REQUEST_URI"]), "/"));
// Homepage
if(isset($route[0]) && $route[0] == "")
{
$model = new HomeModel();
$controller = new HomeController($model);
}
// Nieuws
if(isset($route[0]) && $route[0] == "nieuws")
{
$model = new NewsitemModel();
$controller = new NewsitemController($model);
if(isset($route[1]) && $route[1] != "")
{
// Nieuws detail
$controller->getByTitle($route[1]);
}
else
{
// Nieuws overview
$controller->getAll();
}
}
?>
include "includes/config.php";
$route = explode("/", trim(strtolower($_SERVER["REQUEST_URI"]), "/"));
// Homepage
if(isset($route[0]) && $route[0] == "")
{
$model = new HomeModel();
$controller = new HomeController($model);
}
// Nieuws
if(isset($route[0]) && $route[0] == "nieuws")
{
$model = new NewsitemModel();
$controller = new NewsitemController($model);
if(isset($route[1]) && $route[1] != "")
{
// Nieuws detail
$controller->getByTitle($route[1]);
}
else
{
// Nieuws overview
$controller->getAll();
}
}
?>
Gewijzigd op 23/11/2014 00:38:42 door Roy B
Verder laat je de applicatie nu allerlei models en controllers langslopen, maar kun je dat ook overdragen aan een front controller.
Schematisch krijg je dan zoiets:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
// HTTP-verzoek
$request = new Request();
// Verzoek centraal opslaan
$registry = new Registry();
$registry->set('request', $request);
// Front controller
$front_controller = new FrontController($registry);
// Het verzoek bepaalt de route
if ($request->hasRoute()) {
$route = $request->getRoute();
} else {
$route = new Route('error/404');
}
// Front controller zet andere controller aan het werk
$front_controller->dispatch($route);
?>
// HTTP-verzoek
$request = new Request();
// Verzoek centraal opslaan
$registry = new Registry();
$registry->set('request', $request);
// Front controller
$front_controller = new FrontController($registry);
// Het verzoek bepaalt de route
if ($request->hasRoute()) {
$route = $request->getRoute();
} else {
$route = new Route('error/404');
}
// Front controller zet andere controller aan het werk
$front_controller->dispatch($route);
?>
Je steekt daarnaast steeds een model in een controller, bijvoorbeeld:
Code (php)
1
2
3
4
5
6
7
2
3
4
5
6
7
<?php
$model = new HomeModel();
$controller = new HomeController($model);
$model = new NewsitemModel();
$controller = new NewsitemController($model);
?>
$model = new HomeModel();
$controller = new HomeController($model);
$model = new NewsitemModel();
$controller = new NewsitemController($model);
?>
Als elke FooController in jouw architectuur echter een bijbehorend FooModel heeft, kun je ook alleen de controller aanroepen. In MVC wil je liever alleen controllers met andere controllers laten communiceren. Jij hebt echter nu naast die bovenwereld nog een schaduwwereld: je moet parallel steeds alle models instantiëren voordat je überhaupt een controller aan het werk kunt zetten.
Uit: HMVC: The layered pattern for developing strong client tiers
Verder is dat ook niet efficiënt. De controller bepaalt (bijvoorbeeld op basis van het request) of het model eigenlijk wel nodig is. Sommige models zijn slechts nodig als je een UPDATE of een INSERT wilt uitvoeren. Die models hoef je dus lang niet altijd te laden en te instantiëren.
Ward van der Put op 23/11/2014 06:37:15:
Je steekt daarnaast steeds een model in een controller
Hoe zou dat beter kunnen dan?
Alleen de controller weet waar Abraham de mosterd haalt. Dat kan bijvoorbeeld data uit een database zijn, maar diezelfde data kunnen ook uit een bestand komen, uit een cache, uit een cURL-request, enzovoort. Dat moet allemaal voorbij gaan aan andere objecten. Die spreken de controller gewoon aan en krijgen het antwoord dat ze verwachten.
Je hebt die regel nu omgekeerd met een "voorschrift": je instantieert eerst een model en schrijft dat vervolgens voor aan de controller door het te injecteren in de controller.
Ward van der Put op 23/11/2014 16:13:53:
Vuistregel: alleen de controller communiceert met het eigen model.
Alleen de controller weet waar Abraham de mosterd haalt. Dat kan bijvoorbeeld data uit een database zijn, maar diezelfde data kunnen ook uit een bestand komen, uit een cache, uit een cURL-request, enzovoort. Dat moet allemaal voorbij gaan aan andere objecten. Die spreken de controller gewoon aan en krijgen het antwoord dat ze verwachten.
Je hebt die regel nu omgekeerd met een "voorschrift": je instantieert eerst een model en schrijft dat vervolgens voor aan de controller door het te injecteren in de controller.
Alleen de controller weet waar Abraham de mosterd haalt. Dat kan bijvoorbeeld data uit een database zijn, maar diezelfde data kunnen ook uit een bestand komen, uit een cache, uit een cURL-request, enzovoort. Dat moet allemaal voorbij gaan aan andere objecten. Die spreken de controller gewoon aan en krijgen het antwoord dat ze verwachten.
Je hebt die regel nu omgekeerd met een "voorschrift": je instantieert eerst een model en schrijft dat vervolgens voor aan de controller door het te injecteren in de controller.
Hoe zou ik dat kunnen verbeteren dan?
Misschien een setModel() methode?
Ja, prima oplossing.
Dat is dan het MVC model waarin ik toch een ernstige tekortkoming zie. In Symfony bijvoorbeeld kun je gewoon vanuit de VIEW nog een andere controller aanroepen. Mijn ervaring is dat dat echt heerlijk werkt. Wat ik maar wil zeggen is dat het MVC niet heilig is en dat het mij persoonlijk om gebruiksgemak gaat. @Ward: Ik zie met die layers niet hoe je nu meerdere views in één response kunt verwerken. (Bijv. layer1 -> algemene pagina layout, layer 2 sidebar en layer 3 de specifieke content van de huidige pagina)
Een view kán een controller aanroepen. Die doen dan plaatselijk in hun beperkte context hun eigen ding. Niets mis mee.
Voorbeeld: de view is een widget die een beoordeling in sterren toont. De view roept daarvoor een controller aan die de beoordeling "ergens" uit een model haalt. Maakt niet uit waar: het kan de eigen database zijn, maar ook een cURL-request naar KiyOh of iets dergelijks.
De complicatie is dat je dergelijke dingen moet samensmeden tot één fraaie webpagina. En dan komt het HMVC-model van pas, met meerdere hiërarchisch gestapelde MVC-componenten. Díé componenten moet je koppelen via hun controllers, niet via hun models.
Ward van der Put op 23/11/2014 16:45:31:
Ja, prima oplossing.
Kun je daar een voorbeeldje van geven?
Misschien zoiets?
Code (php)
1
2
3
4
2
3
4
$controller = new NewsitemController();
$model = new NewsitemModel();
$controller->setModel($model);
$model = new NewsitemModel();
$controller->setModel($model);
Gewijzigd op 23/11/2014 17:52:20 door Roy B
controller A wordt aangeroepen en die roept zijn eigen VIEW aan. Ik neem aan dat de VIEW niet direct output genereert maar eerder een response object vult en dit object dan weer teruggeeft aan de controller?
Daarna roept controller A controller B aan? Deze roept ook weer zijn VIEW aan en deze geeft ook een response object terug? Dan zitten we dus met twee response objecten??
Yep, goed gezien, vooral dat zit in de weg.
Daarvoor hebben we dan een registry met één request en één response.
Daarom hebben we ook de neiging om het request/response-systeem te modelleren als een besturingssysteem.
1. Er komt één request binnen. Helder: dít is ons HTTP-verzoek, hier moeten we een antwoord op vinden.
Van dat request is er maar één, dus we hebben een uniek instance die we centraal kunnen registreren.
2. Van die request moeten onderdelen worden gedelegeerd aan specifieke controllers.
Hier komt het op creativiteit aan. Alleen de webdeveloper weet wat het systeem met specifieke requests moet doen.
3. Er komt één response uit.
We kunnen HTTP-output genereren of een HTTP-statuscode, maar veel alternatieven biedt HTTP ons niet.
De request wordt beantwoord met een response óf een code voor een ongeldig request.
@Roy: In het systeem dat Ward beschrijft gaat het inderdaad om die manier. Ward geeft dat ook duidelijk aan:
Het enige verschil tussen jouw voorbeeld en die van Ward is dat Ward het Model aan de constructor meegeeft en jij daar een aparte method voor aanroept.
In andere MVC's wordt het model in de controller bepaald. Je krijgt dan zoiets:
dispatcher:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
<?php
$route = new Route('NewsItem', 'index');
$controllerName = $route->getController(); // geeft NewsItemController
$methodName = $route->getAction(); // geeft indexAction
$controller = new $controllerName();
$controller->$methodName($route->getVars());
?>
$route = new Route('NewsItem', 'index');
$controllerName = $route->getController(); // geeft NewsItemController
$methodName = $route->getAction(); // geeft indexAction
$controller = new $controllerName();
$controller->$methodName($route->getVars());
?>
Controller:
Het Model in de Controller meegeven is doorgaans de beste optie dan?
Het probleem waar ik een beetje tegen aanloop is welke method moet ik nu aanroepen in het Model van Ward.
Je krijgt namelijk het hele object geïnjecteerd in je Controller maar in dat object kunnen twintig verschillende methods zitten...
Code (php)
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
<?php
// Variant A
$user_model = new UserModel();
$user_controller = new UserController($user_model);
// Variant B
$user = new User();
?>
// Variant A
$user_model = new UserModel();
$user_controller = new UserController($user_model);
// Variant B
$user = new User();
?>
Bij variant A moet je overal steeds in de weer met twee objecten: model en controller. Bij variant B heb je maar één API: je spreekt de controller aan en die zoekt verder zelf maar uit of er een model nodig is, hoe dat model er uitziet en hoe met het model wordt gecommuniceerd.
Bij variant A kun je makkelijker verschillende combinaties van models en controllers maken, wat met name bij frameworks handig kan zijn. Je zou in het voorbeeld UserModel door AdminModel kunnen vervangen. Bij variant B is die logica verborgen, wat zowel een voordeel als een nadeel kan zijn.
Variant B leent zich ook wat beter voor het inzetten en veranderen van design patterns: een blanco new User() kun je op verschillende manieren bouwen, terwijl er bij een new User(\UserModel $model) al veel meer is voorgeschreven.
Het is dus inderdaad een keuze.
Gewijzigd op 24/11/2014 07:32:29 door Ward van der Put
Ik zal ook even een spreekwoordelijke duit in het spreekwoordelijke zakje doen.
Wanneer geven we waardes of een object mee aan de constructor? Op het moment dat een class kan werken met dynamische data. Zo kan een database class via de constructor verschillende "verbindingsobjecten" binnenkrijgen om zodoende met verschillende databases contact te kunnen maken, en een e-mailer class kan bijvoorbeeld verschillende e-mailadressen ontvangen om naar verschillende personen te kunnen mailen.
Praten we over een model, dan ligt de situatie echter wat anders. Mijns insziens is een model niets meer dan een verlengstuk van een controller. De code die in het model staat, zouden we ook rechtstreeks in de controller kunnen plaatsen, maar dat doen we niet omdat we alles netjes gescheiden willen houden. En ... mochten we ooit een ander model gaan gebruiken, dan hoeven we niet alle code in de class zelf aan te passen.
Het klinkt natuurlijk leuk om het model via de constructor mee te geven, want dan hoeven we de class nooit aan te passen als we een ander model willen gebruiken. Maar ... is het logisch? Ik vind van niet. Een model is namelijk afgestemd op de controller. De controller en het model zijn onlosmakelijk met elkaar verbonden. Ze horen bij elkaar. Ze zijn als Bert en Ernie, of als Bassie en Adriaan ... maar net waar je zelf de voorkeur aan geeft. Het is dan ook vreemd om van buitenaf te gaan bepalen welk model er nodig is. Dat hoort niet. Als "buitenstaander" praat je enkel met de controller, en die controller bepaalt zelf welk model er nodig is. Het is de controller die dit bepaalt, en niet de "buitenstaander".
Op het moment dat je een AdminModel wilt gebruiken in een UserController zoals Ward hierboven aangeeft, zou het raar zijn om als "buitenstaander" zelf een model te kiezen en dat mee te geven aan de constructor van de UserController. Wie zegt dat de UserController wel overweg kan met dat door de "buitenstaander" gekozen model? Logischer zou het dan zijn om in de constructor van de UserController een parameter in te bouwen:
Code (php)
Op deze manier houd je de beslissingsbevoegdheid bij de controller zelf.
Bottomline: als "buitenstaander" praat je enkel met de controller, en beslis je niet welk model de controller moet gebruiken. Deze bevoegdheid ligt bij de controller zelf.
Amen ... :)