Also, is a view 'allowed' to talk to (eg: get data from) a controller (for example: by calling a method that controller owns)?
Best practice is no, but a controller is allowed to pass a data to the view. Check out templating engines like Mustache.
I'm trying to have some kind of MVC hierarchy here (I'm a student learning about programming 'best practices' in my own time), my question is: would 'index.php' be a view? Or, would the instance of the page class be the view (as it constructs the page, but it doesn't actually show it, it returns a string), or: would the 'page' class be a controller?
Usually index.php become the front controller, or the bootstrap.
You create some folders: library, controllers, models, and views, then you load all them from the index.php.
Then you make a Route class in your libraries folder, that should be able parse the PHP REQUEST_URI into a controller class name, method name, and an array of parameters.
In your index.php, you could instantiate a new Route with the PHP REQUEST_URI to it, then you call the Route::build method, that should be able instiate a controller then call it's method that correspond to the parsed PHP REQUEST_URI. (Learn about call_user_func_array).
The controller's method will returns a string (from a view file, or whatever), then you could just print it to the browser.
Here i'll just give you a minimum example of MVC in PHP:
core.php
<?php
class Route
{
protected $class;
protected $method;
protected $params = array();
/**
* Parse the given URI, if the URI is "site/article/10", then "site" will be
* the controller class, "article" will be the method, and "10" will be
* the parameter.
*/
function __construct($uri)
{
$segments = explode('/', $uri);
foreach ($segments as $segment)
{
if ($this->class === NULL)
{
$this->class = ucfirst($segment);
}
else if ($this->method === NULL)
{
$this->method = $segment;
}
else
{
$this->params[] = $segment;
}
}
}
/**
* Instantiate a controller class then execute the method with the parameters from
* the parsed REQUEST_URI.
*/
function call(Database $database)
{
$controller = new $this->class($database);
$response = call_user_func_array(array($controller, $this->method), $this->params);
return $response;
}
}
class Controller
{
function __construct(Database $database)
{
$this->database = $database;
}
/**
* Instantiate a model
*/
function model($name)
{
$model = new $name($this->database);
return $model;
}
}
class View
{
protected $data = array();
protected $path;
function __construct($path, array $data = array())
{
$this->path = $path;
$this->data = $data;
}
function render()
{
// Extract the data so you can access all the variables in
// the "data" array inside your included view files
ob_start(); extract($this->data);
try
{
include $this->path;
}
catch(\Exception $e)
{
ob_end_clean(); throw $e;
}
return ob_get_clean();
}
/**
* Make it able for you to write a code like this: echo new View("home.php")
*/
function __toString()
{
return $this->render();
}
}
class Model
{
protected $database;
function __construct(Database $database)
{
$this->database = $database;
}
}
class Database
{
function __construct()
{
// Connect to the database, for example: mysql_connect($host, $user, $pass, $data)
}
function query($sql)
{
// Do some database query like usual, for example: mysql_query($sql)
}
}
index.php:
<?php
require "core.php"
class Article extends Model
{
function find($id)
{
// You execute query through $this->db->query("SELECT..."), but i'll just use an array
// to simplify things.
return array("title" => "Article", "body" => "Lorem Ipsum");
}
}
class Site extends Controller
{
function article($id)
{
$article_model = $this->model("Article");
// This is how you use the view class!
$view = new View("article.php", array("article" => $article_model->find($id)));
return $view;
}
}
// This is where everything starts
$route = new Route("site/article/10");
echo $route->call(new Database());
article.php:
<h1><?php echo $article['title'] ?></h1>
<p><?php echo $article['body'] ?></p>
Put article.php, index.php, and core.php in one folder then run the index.php from your browser.