All,
I am studying some sample code given in my web dev class as an example of MVC (again, for the web). In this code, there's a system to navigate from the index.php page to the various controllers (which then call the Model and View modules), and then back into index.php.
I understand how the MVC works.
What I'm grappling with is the navigation mechanism. I am having difficulties understanding how all the pieces work together.
Could anyone take a look at the code below and tell me if this matches a well known method / pattern to deal with dynamic website navigation? (Maybe the Front Controller?) If it does, then my hope is that I can more easily do some more research on it.
Many thanks!
JDelage
Index.php
<?php
require_once("User.php");
session_start();
if (isset($_GET['action']))
$action= $_GET['action'];
else
$action="";
switch ($action) {
case 'login':
require_once('Login.php');
$command= new LoginControler();
break;
case 'logoff':
require_once('Logoff.php');
$command= new LogoffControler();
break;
// Several other cases
default:
require_once('Unknown.php');
$command= new UnknownControle();
}
$command->execute();
require_once('EntryMenu.php'); // Those are objects that represent both the
// menu label and the links.
$menu= array(
new EntryMenu("Login", "index.php", array("action" => "logon")),
new EntryMenu("Logoff", "index.php", array("action" => "logoff")),
new EntryMenu("Write", "index.php", array("action" => "write")),
new EntryMenu("Read", "index.php", array("action" => "read"))
);
if ($command->redirect) {
header('Location: ' . $command->redirect);
} else if ($command->page) {
include("ui/header.php");
include("ui/menu.php");
echo "<div class='content'>";
include("ui/". $command->page);
echo "</div>";
include("ui/footer.php");
}
?>
Controler.php
<?php
class Controler {
public $page= "problem.php";
function execute() {}
}
?>
LogoffControler.php
<?php
require_once('Controler.php');
class LogoffControler extends Controler {
function execute() {
$this->redirect= "index.php";
unset($_SESSION['user']);
}
}
?>
LoginControler.php
<?php
require_once('LoginModel.php'); // This manages the exchanges with the user db
require_once('Controler.php');
class ConnexionControle extends Controler {
public $page= "LoginForm.php";
function execute() {
// More code to deal with incorrectly filled login forms
$login = new LoginModel();
$login->loginUser($_POST['login'], $_POST['password']);
if ($login->userLogedIn()) {
$_SESSION['user']= $login->user;
$this->redirect= "index.php";
}
// More code to deal with invalid logins
}
}
?>
I am assuming you understand the controller part, and is asking about the switch..case statements. I haven't come across an official name for that yet,but most MVC frameworks for PHP (Kohana, CakePHP, CodeIgniter, Fat Free and etc.) calls that 'routing'. It's mapping of a URL to a controller.
Using a switch..case sets of statement is one of the easier ways. More sophisticated solutions use RegEx to match pre-defined URL patterns to resolve what controller to invoke, and what are its parameters (usually bundled as a 'request')
Other methods include using URL rewriting to come up with pretty urls, such as /articles/month/nov/article-id/3
which in 'ugly url form' is :
action=articles&month=nov&article-id=3
If you would like an easy-to-dissect verion of a MVC system you could try the 1kb PHP MVC which handles everything you are attempting in a much cleaner fashion. Though you might have to break up the code if you really want to read it as it is in compressed form.
With this system you simply place a controller in /classes/controller/ named somthing.php and you can then access it from the URL like http://site.com/something.
Loading Models is also easy and doesn't require any include or require calls.
class Controller_Something
{
public function index()
{
$model = new Model_User();
}
}
Related
For example in a Yii Framework application the url is in this format
www.example.com/index.php?r=foo/bar
Which renders the script inside the actionBar() method of class FooController. Further, this class (or its parent class) implements a render() method which can render a view file.
All the url's are handled through the entry script index.php.
I would like to write my own class which can handle url's through this way.
Can someone give me a very basic 'hello world' example of writing such a script ?
I'll give it a shot:
// index.php
$r = $_REQUEST['r']; // 'foo/bar'
$rParts = explode('/',$r);
$foo = $rParts[0];
$bar = $rParts[1];
$controller = new $foo; // foo
echo $controller->$bar();
Here is what I did for a friend recently, when teaching him how frameworks works. This is a basic example, but it demonstrates how a container works, how to handle the router, giving the controller a request and a response and handling redirects and the like.
<?php
require 'autoload.php';
$container = [];
$container['controller.elephant'] = function() {
return new Controller\Elephant();
};
$routes = [];
$routes['/babar'] = 'controller.elephant:babar';
$routes['/celeste'] = 'controller.elephant:celeste';
$request = new Request();
if (!isset($routes[$request->path()])) {
http_response_code(404);
exit;
}
$route = $routes[$request->path()];
list($class, $method) = explode(':', $route);
$controller = $container[$class]();
$response = $controller->{$method}($request, new Response());
if ($response->isRedirect()) {
http_response_code($response->status());
header('Location: '.$response->destination());
} else {
echo $response->content();
}
exit;
I won't include anything more than that (albeit there is other files) because it would bloat the answer needlessly (I can send them to you by other means if you want to).
I highly advise you to look at the Slim Framework code, as it is a micro framework that basically do just that.
Well in the Symfony documentation you have this page: http://symfony.com/doc/current/components/http_kernel/introduction.html
where it explains how is the life cycle of a request, it's just a flow diagram.
But it will give you a really good idea on how you should build yours
If you are more interested in how based on a url you get the controller you should read the RoutingComponent in symfony
http://symfony.com/doc/current/components/routing/introduction.html
http://symfony.com/doc/current/components/routing/hostname_pattern.html
But if you want to write your own class, you should use something like regex expression groups where you can detect the url parts separated by i.e: '/' then you somehow map the url to the controller i.e associative array 'Hash'
someurl.com/someController/someAction
$mappings = [
...
'someController' => 'The\Controller\Class'
]
$controller = new $mappings[$urlControllerPart]();
$response = $controller->{$urlActionPart}($request);
return $response;
I am building an AJAX web app, using PHP for my back end. I am trying to design a routing system that will let me easily drop new pages in, and let me focus on the Javascript. The actual pages that PHP will be serving up are simple, just views that are essentially containers for Javascript charts (built with d3.js). Thus, my controller won't even have to interact with my model until I start making AJAX calls.
I am new to OOP, especially in back end. I've been doing a bit with Javascript, but I am brand new to incorporating OOP with MVC & solving the issue of routing. I know there are modules/plugins out there that have Routing classes written, but as the back end part of this project is very straight-forward - essentially, how best to serve up an 'About' page on a blog - I'd like to take this opportunity to learn it thoroughly myself.
I have one controller:
<?php
//controller.php
include 'views/view.php';
class Controller
{
public function homeAction() {
$view = new View();
$view->setTemplate('views/home.php');
$view->render();
}
public function categoryAction($category) {
$view = new View();
$view->setTemplate("views/Monitor/{$category}/{$category}.php");
$view->setCategory($category);
$view->render();
}
public function monitorAction($category, $monitor) {
$view = new View();
$view->setTemplate("views/Monitor/{$category}/{$monitor}.php");
$view->setCategory($category);
$view->setMonitor($monitor);
$view->render();
}
}
?>
Right now, I instantiate my controller at the beginning of index.php:
<?php
// Load libraries
require_once 'model.php';
require_once 'controller.php';
$controller = new Controller();
$uri = str_replace('?'.$_SERVER['QUERY_STRING'], '', $_SERVER['REQUEST_URI']);
// home action
if ($uri == '/') {
$controller->homeAction();
// /{category}/{monitor}
} elseif (preg_match("#/(.+)/(.+)#", $uri, $matches) ) {
$category = $matches[1];
$monitor = $matches[2];
$controller->monitorAction($category, $monitor);
// /{category}
} elseif (preg_match("#/([^/.]+)#", $uri, $matches) ) {
$category = $matches[1];
$controller->categoryAction($category);
// 404
} else {
header('Status: 404 Not Found');
echo '<html><body><h1>Page Not Found</h1></body></html>';
}
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && (!empty($_GET)) && $_GET['action'] == 'get_data') {
$function = $_GET['chart'] . "_data";
$dataJSON = call_user_func($function);
header('Content-type: application/json');
echo $dataJSON;
}
?>
I have read a bit about PHP's autoloader, but I'd like to get it down manually first, because I want to make sure and understand the fundamentals.
Is this the appropriate place to instantiate my Controller object?
First, your architecture is facing some major problems. You need a router to take care of your requested URIs by the users and next you need an initialization state for your system. The usual way to create Controllers is to extend a parent class, then in your parent class __construct method you can initialize your children controllers, however, your system isn't in a good shape.
This is a gold link that I never delete:
http://johnsquibb.com/tutorials/mvc-framework-in-1-hour-part-one
I recently implemented the following MVC code using this tiny mvc boilerplate.
I did not want to use Zend or Symfony as I only require a small structure but i really need to expend this one slightly.
I am new to PHP so wondered if anyone has used this or knows how i go about adding another View. I have got the link version working which i use to load my layout but would like to add a Content section within this layout which is able to call other pages.
Any help with this would be great!
Gods below .. that video is horrible.
In that existing example, if you want to add another "view" (which is no really what view is), you will need another method in the controller:
class Controller
{
// -- snip --
// you need to change the constructor too
public function __construct()
{
$this->load = new Load;
$this->model = new Model;
}
// -- snip --
public function gallery()
{
$list = $this->model->get_urls();
if ( count($list) > 0 )
{
$this->load->view('gallery.php' , $list);
}
else
{
$this->load->view('error.php', array(
'source' => 'gallery',
'reason' => 'empty'
));
}
}
// -- snip --
}
And you also would need to change the tinyMvc.php file:
$c = new Controller;
$action = 'home';
if ( isset( $_GET['page']))
{
$action = $_GET['page'];
}
if ( method_exists( $c, $action) )
{
$c->{$action}();
}
else
{
echo 'no such action !';
}
Anyway. Whole that "tutorial" uses the terms of MVC, that isn't really whats made there. His "view" is actually just a simple template. Which is not completely a thing to learn how to do for a beginner, but his implementation sucked too .. If you want to learn how to make simple native php templates, you might find this article quite useful.
Hi i wont to make something like that.
http:// example.com/ - Main Controller
http:// example.com/rules/ - Main Controller where content get from database, but if not exist
return 404 page. (It's ok, isn't problem.)
But if i have subfolder in application/controlles/rules/
I want to redirect it to Main Contorller at Rules folder.
This follow code can solve problem, but i don't know how it right realise.
At routes.php:
$route['default_controller'] = "main";
$route['404_override'] = '';
$dirtest = $route['(:any)'];
if (is_dir(APPPATH.'controllers/'.$dirtest)) {
$route['(:any)'] = $dirtest.'/$1';
} else {
$route['(:any)'] = 'main/index/$1';
}
Ok, what I have:
controllers/main.php
class Main extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('main_model');
}
public function index($method = null)
{
if (is_dir(APPPATH.'controllers/'.$method)) {
// Need re-rout to the application/controllers/$method/
} else {
if ($query = $this->main_model->get_content($method)) {
$data['content'] = $query[0]->text;
// it shows at views/main.php
} else {
show_404($method);
}
}
$data['main_content'] = 'main';
$this->load->view('includes/template', $data);
}
}
Updated Again (routes.php):
So, seem's like what i search (work example):
$route['default_controller'] = "main";
$route['404_override'] = '';
$subfolders = glob(APPPATH.'controllers/*', GLOB_ONLYDIR);
foreach ($subfolders as $folder) {
$folder = preg_replace('/application\/controllers\//', '', $folder);
$route[$folder] = $folder.'/main/index/';
$route[$folder.'/(:any)'] = $folder.'/main/$1';
}
$route['(:any)'] = 'main/index/$1';
But, in perfectly need some like this:
http:// example.com/1/2/3/4/5/6/...
Folder "controllers" has subfolder "1"?
YES: Folder "1" has subfolder "2"?
NO: Folder "1" has controller "2.php"?
NO: Controller "controllers/1/main.php" has function "2"?
YES: redirect to http:// example.com/1/2/ - where 3,4,5 - parameters..
It is realy nice, when you have structure like:
http://example.com/blog/ - recent blog's posts
http://example.com/blog/2007/ - recent from 2007 year blog's posts
http://example.com/blog/2007/06/ - same with month number
http://example.com/blog/2007/06/29/ - same with day number
http://example.com/blog/web-design/ - recent blog's post's about web design
http://example.com/blog/web-design/2007/ - blog' posts about web design from 2007 years.
http://example.com/blog/current-post-title/ - current post
Same interesting i find http://codeigniter.com/forums/viewthread/97024/#490613
I didn't thoroughly read your question, but this immediately caught my attention:
if (is_dir($path . '/' . $folder)) {
echo "$route['$folder/(:any)'] = '$folder/index/$1';"; //<---- why echo ???
}
Honestly I'm not sure why this didn't cause serious issues for you in addition to not working.
You don't want to echo the route here, that will just try to print the string to screen, it's not even interpreted as PHP this way. There are also some issues with quotes that need to be remedied so the variables can be read as variables, not strings. Try this instead:
if (is_dir($path . '/' . $folder)) {
$route[$folder.'/(:any)'] = $folder.'/index/$1';
}
Aside: I'd like to offer some additional resources that are not directly related to your problem, but should help you nonetheless with a solution:
Preferred way to remap calls to controllers: http://codeigniter.com/user_guide/general/controllers.html#remapping
Easier way to scan directories: http://php.net/manual/en/function.glob.php
It's hard to say why the registering of your routes fails. From your code I can see that you're not registering the routes (you just echo them), additionally I see that the usage of variables in strings are used inconsistently. Probably you mixed this a bit, the codeigniter documentation about routes is not precise on it either (in some minor points, their code examples are not really syntactically correct, but overall it's good).
I suggest you first move the logic to register your dynamic routes into a function of it's own. This will keep things a bit more modular and you can more easily change things and you don't pollute the global namespace with variables.
Based on this, I've refactored your code a bit. It does not mean that this works (not tested), however it might make things more clear when you read it:
function register_dynamic_routes($path, array &$route)
{
$nodes = scandir($path);
if (false === $nodes)
{
throw new InvalidArgumentException(sprintf('Path parameter invalid. scandir("$path") failed.', $path));
}
foreach ($nodes as $node)
{
if ($node === '.' or $node === '..')
continue
;
if (!is_dir("{$path}/{$node}")
continue
;
$routeDef = "{$folder}/(:any)";
$routeResolve = "{$folder}/index/\$1";
$route[$routeDef] = $routeResolve;
# FIXME debug output
echo "\$route['{$routeDef}'] = '{$routeResolve}';";
}
}
$path = APPPATH.'controllers/';
register_dynamic_routes($path, $route);
$route['(:any)'] = 'main/index/$1';
Next to this you probably might not want to shift everything onto the index action, but a dynamic action instead. Furthermore, you might want to have a base controller that is delegating everything into the sub-controllers instead of adding the routes per controller. But that's up to you. The example above is based on the directory approach you outlined in your question.
Edit: Additional information is available in the Controllers section next to the URI Routing section
All this seems kind of complicated.
Plus, if you have hundreds (or thousands or more?) of possible routes in a database you may not want to load all of them into the "$routes" array every time any page loads in your application.
Instead, why not just do this?
last line of routes.php:
$route['404_override'] = 'vanity';
Controller file: Vanity.php:
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Vanity extends MY_Controller {
/**
* Vanity Page controller.
*
*/
public function __construct() {
parent::__construct();
}
public function index()
{
$url = $_SERVER['PHP_SELF'];
$url = str_replace("/index.php/", "", $url);
// you could check here if $url is valid. If not, then load 404 via:
//
// show_404();
//
// or, if it is valid then load the appropriate view, redirect, or
// whatever else it is you needed to do!
echo "Hello from page " . $url;
exit;
}
}
?>
OK, so I'm trying to teach myself the CakePHP framework, and I'm trying to knock up a simple demo app for myself.
I have the controllers, views and models all set up and working, but I want to do something slightly more than the basic online help shows.
I have a guitars_controller.php file as follows...
<?php
class GuitarsController extends AppController {
var $name = 'Guitars';
function index() {
$this->set('Guitars', $this->Guitar->findAll());
$this->pageTitle = "All Guitars";
}
function view($id = null) {
$this->Guitar->id = $id;
$this->set('guitar', $this->Guitar->read());
// Want to set the title here.
}
}
?>
The 'Guitar' object contains an attribute called 'Name', and I'd like to be able to set that as the pageTitle for the individual page views.
Can anyone point out how I'd do that, please?
NB: I know that there is general disagreement about where in the application to set this kind of data, but to me, it is data related.
These actions are model agnostic so can be put in your app/app_controller.php file
<?php
class AppController extends Controller {
function index() {
$this->set(Inflector::variable($this->name), $this->{$this->modelClass}->findAll());
$this->pageTitle = 'All '.Inflector::humanize($this->name);
}
function view($id = null) {
$data = $this->{$this->modelClass}->findById($id);
$this->set(Inflector::variable($this->modelClass), $data);
$this->pageTitle = $data[$this->modelClass][$this->{$this->modelClass}->displayField];
}
}
?>
Pointing your browser to /guitars will invoke your guitars controller index action, which doesn't exist so the one in AppController (which GuitarsController inherits from) will be run. Same for the view action. This will also work for your DrumsController, KeyboardsController etc etc.
You can set this in the controller:
function view($id = null) {
$guitar = $this->Guitar->read(null, $id);
$this->set('guitar', $guitar);
$this->pageTitle = $guitar['Guitar']['name'];
}
Or in the view:
<? $this->pageTitle = $guitar['Guitar']['name']; ?>
The value set in the view will override any value that may have already been set in the controller.
For security, you must ensure that your layout / view that displays the pageTitle html-encodes this arbitrary data to avoid injection attacks and broken html
<?php echo h( $title_for_layout ); ?>
In response to your own answer about oo paradigm. Its like this :
function view($id) {
$this->Guitar->id = $id;
$this->Guitar->read();
$this->pageTitle = $this->Guitar->data['Guitar']['name'];
$this->set('data', $this->Guitar->data);
}
By the way, you should check if id is set and valid etc, since this is url user input.
As of CakePHP 1.3, setting page title has been changed.
$this->pageTitle = "Title"; //deprecated
$this->set("title_for_layout",Inflector::humanize($this->name)); // new way of setting title
Note: More about Inflector: http://api13.cakephp.org/class/inflector#method-Inflector
$this->pageTitle = $this->Guitar->Name;
It should go in the View though, I don't PHP, or cakePHP, but thats something a view should do, not the controller.
It should go in the controller. See this
"but I want to do something slightly more than the basic online help shows."
Isn't that always the rub? So much documentation is geared towards a bare minimum that it really does not help much. You can complete many of the tutorials available but as soon as you take 1 step off the reservation the confusion sets in. Well, it's either bare minimum or pro developer maximum but rarely hits that sweet spot of ease, clarity and depth.
I'm currently rewriting some Zend Framework documentation for my own use simply so I can smooth out the inconsistencies, clarify glossed over assumptions and get at the core, "best practice" way of understanding it. My mantra: Ease, clarity, depth. Ease, clarity, depth.
Ah, the none-obvious answer is as follows...
$this->pageTitle = $this->viewVars['guitar']['Guitar']['Name'];
I found this by placing the following code in the controller (was a long shot that paid off, to be honest)
echo "<pre>"; print_r($this);echo "</pre>";
Thanks to all those that tried to help.
echo "<pre>"; print_r($this);echo "</pre>";
how about
pr( $this );
OK, I really want to set the page title in the controller instead in the view. So here's what I did:
class CustomersController extends AppController {
var $name = 'Customers';
function beforeFilter() {
parent::beforeFilter();
$this->set('menu',$this->name);
switch ($this->action) {
case 'index':
$this->title = 'List Customer';
break;
case 'view':
$this->title = 'View Customer';
break;
case 'edit':
$this->title = 'Edit Customer';
break;
case 'add':
$this->title = 'Add New Customer';
break;
default:
$title = 'Welcome to '.$name;
break;
}
$this->set('title',$this->title);
}
The trick is that you can't set $this->title inside any action, it won't work. It seems to me that the web page reaches action after rendering, however you can do it in beforeFilter.