PHP MVC which URLs belong to?
I am using PHP application using MVC technique. A quick introduction for the download http://site/photos/25?options
that rewrites http://site/index.php?page=photos&id=25&options
:
-
index.php
-> instantiates and calls thePhotoController
object -
PhotoController
creates an instancePhotoView
and loads the correctPhoto
(model) into it -
PhotoController
callsPhotoView->render()
, and the header / footer views -
PhotoView
has access to the modelPhoto
and other required model data.
PhotoView
outputs HTML and wants to hyperlink to different pages. When this view creates hyperlinks, I am confused about which layer the url should have. The options I see are described below. Please help me choose the right approach.
Model-specific URLs
Models have a method
getURL()
Benefits:
- Base class
View
can include Open Graph properties -
PhotoView
could do this:$folderHref = $photo->getFolder()->url
Disadvantages:
- Some controllers do not have any appropriate model, for example
LoginController
,AdminController
as if I was getting the URL-addresses for them?
The controller owns the URLs
Controllers have properties
url
andtitle
. To create a link, the presenting controller creates target controllers and navigates to the view. The view refers tourl
andtitle
from the passed object.
Benefits:
- Each url maps directly to the controller (s
index.php
), so the reverse seems clean.
Disadvantages:
-
A view that needs URLs for other views would require one of the following ugly hacks:
-
Unbound controller request
$folderHref = Controllers\FolderController::urlForFolder($photo->getFolder())
-
Views knowing too much about their controllers
$folderHref = $photo->getFolder()->getControllerAndSetURL()->getURL()
-
Excessive link between representation and its controller
$folderHref = $this->delegate->getURLForFolder($photo->getFolder())
and$adminHref = $this->delegate->getURLForAdminHref()
withdelegate
a variety of methods
-
All URL owners
The base class
OpenGraphObject
is the parent class forControllers
ANDModels
and has a methodgetURL()
Controllers
and Models
only implement this if it can be run with no arguments. (For example, it PhotoController
returns NULL because the URL depends on what will be displayed Photo
).
Benefits:
- Every advantage is higher
disadvantages
- Confusion
- Revoking my programming license
source to share
To separate concerns, I would break it down like this:
/**
* Responsible for processing and rendering user output
*/
interface View {
public function render();
}
/**
* Used just to further formalize the messages between Photo and View
*/
interface PhotoView extends View {
public function setPhotoData($id, $title, $link);
}
/**
* Among other things knows how to generate URLs to routes
*/
interface Router {
/**
* Returns the URL to route based on route name
* @param string $routeName
* @param array $params
*/
public function urlTo($routeName, $params = null);
}
I'm not going to implement every class, there are many options you can use for example for templating engine and router.
An example of a controller for displaying an id based image could be:
class PhotoController {
public function displayAction($photoId) {
$photo = Photo::findById($photoId);
$templateFile = $this->pathTo('photo-template.php');
//can be a singleton for example, it holds all the routes for the application
//and knows how to render them based on the name and parameters
$router = AppRouter::instance();
return $photo->render(new ConcretePhotoView($templateFile, $router));
}
}
In this example, the photo should be able to fetch itself from the DB and then display itself in the PhotoView:
class Photo {
private $id;
private $title;
private $sourceLink;
//other methods...
public function render(PhotoView $view) {
$view->setPhotoData($this->id, $this->title, $this->sourceLink);
return $view->render();
}
}
Let's say the HTML template photo-template.php
looks like this:
<a href="<?= $userLogin['href'] ?>"><?= $userLogin['label'] ?></a>
<a href="<?= $userRegister['href'] ?>"><?= $userRegister['label'] ?></a>
<div>
<img src="<?= $photo['src'] ?>" title="<?= $photo['title'] ?>" />
<a href="<?= $photoLink['href'] ?>"><?= $photoLink['label'] ?></a>
</div>
It should be clear enough what variables are required for this template. No logic in it, just pure display, although some basic control would be fine.
All that's left is the ConcretePhotoView code that will use the provided template file and router to adapt data between the Photo domain object and the specific html template:
class ConcretePhotoView implements PhotoView {
private $templateEngine;
private $router;
public function __construct($templateFile, Router $router) {
$this->templateEngine = new SomeTemplateEngine($templateFile);
$this->router = $router;
}
public function render() {
//router has a route userLogin with no params
$this->templateEngine->assign('userLogin', array(
'href' => $this->router->urlTo('userLogin'),
'label' => 'Login'
));
$this->templateEngine->assign('userRegister', array(
//router has a route named userRegister that requires no params
'href' => $this->router->urlTo('userRegister'),
'label' => 'Register'
));
return $this->templateEngine->render();
}
public function setPhotoData($id, $title, $link) {
$this->templateEngine->assign('photo', array(
'src' => $link,
'title' => $title
));
$this->templateEngine->assign('photoLink', array(
//router has a route named photo that requires an id
'href' => $this->router->urlTo('photo', array('id' => $id)),
'label' => $title
));
}
}
Finally, to answer the question "Who owns the URLs?" - Router. This knowledge is encapsulated and specific to the application configuration. Other objects use it to create links.
source to share