Avoid large switch statement in frontcontroller - php

I have a larger application with a Frontcontroller in php that handles incoming ajax requests. I am thinking about a good way to handle Action->Method mapping, this controller is in charge of instantiating other classes and executing methods there.
The switch is just getting too big and it's ugly. I was thinking about creating an array and simply doing:
if(in_array($action, $methodmap)){
$methodmap[$action]();
}
But not sure of how efficient that would be or if there are any other better alternatives, performance is important since this controller handles a whole lot of incoming requests.
Thanks!

You could create a simple routing system.
index.php
<?php
class InvalidClassException extends Exception {}
function autoloader($class)
{
$path = 'controllers/'.$class.'.php';
if (!ctype_alnum($class) || !file_exists($path))
throw new InvalidClassException("Couldn't find '$class'");
require($path);
}
spl_autoload_register('autoloader');
$request = isset($_GET['request'])?$_GET['request']:'front';
$controller = new $request();
$controller->index();
And a directory controllers/ where you store all your controllers. E.g.
controllers/test1.php
<?php
class Test1
{
public function index()
{
print "Test 1";
}
}
When accessing index.php?request=test1, Test1->index() would be called, thus output
Test 1

TRy using a "routing" configuration file instead... that way, you can add new routings to the application without needing to change the actual action/method mapping code

Related

Creating an MVC using good practices, and making it testable

I recently asked my girlfriend to teach me how to create a website using PHP and if possible to make me understand frameworks. “Of course” she replied helpfully, and gave me some links and books to help me understand the basics of how it works, and what PHP is.
In my web-grazing I visited many sites, including this one (which appeared whenever I had a serious question not relating to cats) and learned about many things including, but not limited to, classes, autoloading, database connections, evil singletons, and design patterns.. not all of which I totally grasped, but at least I wasn’t like ’uh, what?’ when she mentioned them.
We then sat down and she walked me through the getting started phase of creating my own MVC framework.
We created the following folder structure: (abridged):
app/core/
app/controllers/template/
and routed all requests via index.php, to the router class and on the way loaded an autoload class that registered the classes we will use on the site (currently only Template - in controllers/template/template.php - which only echos ‘Hello Rufferto’)
It was at this point that she told me that she liked to create a router table in the database, rather than keeping the routing info in a file, and said:
“let’s instantiate a database class in the router class where we will read the table info. I have my database class already in the app/core directory, It’s got all the mysql_connect() stuff already, and we only ever need one connection, so we’ll load it here as a singleton."
I of course immediately grabbed all her stuff, and threw it and her out into the street, shouting "Don't come back you evil harridan! I think the people at stackoverflow will have to take it from here’
This does leave me with a small problem. Everything we had done up to that point was now being called into question, and before I go any further with my plan to create an MVC framework that will end world hunger, create peace throughout the land, and make anyone who helps me with this question totally irresistible to every member of the gender they prefer, I want to make sure I haven’t started off with all the wrong ingredients.
I have used my most newly acquired skill - pasting - to put the code so far below, and wish some help with the black clouds hanging over the code, which I have put, as I see them, below said code....
index.php:
<?php
require_once(__DIR__ . '/app/config.php');
\APP\Core\Router::create();
config.php:
require_once 'core/autoloader.php';
autoloader.php:
<?php
namespace APP\Core;
class Autoloader
{
public static $loader;
public static $namespaces = array(
'APP\Core' => array('app/core'),
'APP\Models' => array('app/models'),
'APP\Controllers' => array('app/controllers')
);
public static function init()
{
if (self::$loader == null) {
self::$loader = new self();
}
return self::$loader;
}
public function __construct()
{
spl_autoload_register(function ($class_name) {
foreach (Autoloader::$namespaces as $current_namespace => $paths) {
//iterate through each namespace and add the classes - code removed for brevity - it working fine!
}
});
}
}
Autoloader::init();
router.php:
<?php
namespace APP\Core;
class Router
{
function __construct()
{
// crazy ex girfriend instantiated databse here, ive moved it since i want to use PDO
}
public static function create($path='')
{
$cl=get_called_class();
$class = new $cl;
if(empty($path)) $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$query = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
$db_opt = [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
\PDO::ATTR_EMULATE_PREPARES => false,
];
$db = new \PDO(STD_PDO_DSN, DB_USER, DB_PASSWD, $db_opt);
$sql = "SELECT * FROM `router` where (regex=1 and ? REGEXP `source`) or (regex=0 and `source` = ?) order by `order` asc";
$stmt = $db->prepare($sql);
$stmt->execute([$path, $path]);
$redirect = $stmt->fetch();
if($redirect){
// code here just makes params array from the table - stuff like a body class, meta title etc
$class_redirect::create($params)->run();
}
}else{
echo 'you're an idiot, and should just flip burgers instead';
}
}
}
template.php:
<?php
namespace APP\Controllers\Template;
class Template
{
public function process_some_action()
{
// Like dealing with a post containing new baby names
}
public function process_default()
{
// Do incredible default stuff like list baby names
}
public static function create($params=array())
{
$class = get_called_class();
return new $class($params);
}
function run($params=array())
{
$processed=false;
$processed=$this->call_if_exists("process_some_action");
if(!$processed){
$processed=$this->call_if_exists("process_default");
}
if($processed){
$this->view();
} else {
echo 'Oh ffs Groo';
}
}
function call_if_exists($method_name)
{
if(method_exists($this, $method_name)){
$this->$method_name();
return true;
} else {
return false;
}
}
function view()
{
echo '<br/>';
echo 'Hello Rufferto!';
echo '<br/>';
}
}
1: am I calling the autoloader correctly - is it ok to put it in the ‘config’ file, or should I be putting it somewhere else?
2: er, is it a singleton? If so how can I fix that?
3: I want to use PDO, and have seen a couple of posts directing the questioner to ‘get rid of the useless database class…. But surely what I am looking for here is a ‘class’ that contains the code to make a connection, and have all the controllers or models that need it just call it (and if it doesn’t exist already create it)?
4: I am not convinced I understand the point of the 'create()' function in template (or router for that matter)....
Of course there may be other things wrong, and feel free to point them out to me, but these 4 things are the root of my issue, as I have been going round in circles, renaming things, getting into a state because I’m no longer sure if things are singletons, calling classes that wrap the PDO stuff, then scrapping it all and starting again.
No, there’s no models or views …. I’m not there yet (I will almost certainly use xslt unless the internet breaks with all the stackers shouting at me that it’s moronic) and 1 last thing…… I am not yet comfortable enough with my understanding of dependency injection to use it in any sentence other than this one.
P.S. Absolutely the 1 thing I will never do is call the ex and tell her I’m stuck…….
P.P.S If you have got this far, here’s a treat: http://img1.joyreactor.com/pics/post/comics-threepanelsoul-programmer-job-653145.png
This question would be better suited for codereview. SO is more for "this code doesn't work, what did I do wrong?" questions, rather than "this code works, but can it be improved or am I even doing the right thing?" type questions.
Having said that:
Usually, config files are just that: files containing configuration details. Think database credentials, API keys for third party resources, that sort of thing. You should NOT instantiate classes there. Most full frameworks include a Bootstrap class that handles that sort of thing: initialising the application, loading the config file, autoloader and routes, etc.
Your autoloader is currently a static class with only static methods. That's one way of making a singleton. There's nothing really wrong with that, because you'll only ever want one autoloader in your application.
I suggest you look into some ORM-type libraries, like Doctrine. I've just started work on a project that uses CakePHP's ORM library. If you want to build it yourself, I recommend writing an abstract base model class that has all the logic for retrieving, saving and deleting entries from the database, where each model class itself has a few properties that are unique per model (such as the database table name, for example) and extends this base model.
Those ::create() functions would be useful in a singleton, where they return the existing object if it has already run or instantiate a new one if not, (see example below). Since you seem to have a dislike for singletons, you might want to think about dropping the create() method and just doing the initial setup in the constructor.
Additionally, I wouldn't recommend XSLT for templating. It's a pain, because you'll have to make sure all your controllers build an XML file for your content. If you're going to use templates, look into existing template engines like Twig or Smarty.
Here's that example I promised about using a create() function for a singleton:
class Template {
private static $instance;
public static function create($params = array()) {
if (self::$instance !== null) {
return self::$instance;
}
self::$instance = new static($params);
// do additional setup for the new instance here
return self::$instance;
}
}
Although I'd personally use the name get() instead of create().

Alternatives to static methods in a framework PHP

Lately I have been trying to create my own PHP framework, just to learn from it (As we may look into some bigger and more robust framework for production). One design concept I currently have, is that most core classes mainly work on static functions within classes.
Now a few days ago, I've seen a few articles about "Static methods are death to testability". This concerned me as.. yeah.. my classes contain mostly static methods.. The main reason I was using static methods is that a lot of classes would never need more than one instance, and static methods are easy to approach in the global scope. Now I'm aware that static methods aren't actually the best way to do things, I'm looking for a better alternative.
Imagine the following code to get a config item:
$testcfg = Config::get("test"); // Gets config from "test"
echo $testcfg->foo; // Would output what "foo" contains ofcourse.
/*
* We cache the newly created instance of the "test" config,
* so if we need to use it again anywhere in the application,
* the Config::get() method simply returns that instance.
*/
This is an example of what I currently have. But according to some articles, this is bad.
Now, I could do this the way how, for example, CodeIgniter does this, using:
$testcfg = $this->config->get("test");
echo $testcfg->foo;
Personally, I find this harder to read. That's why I would prefer another way.
So in short, I guess I need a better approach to my classes. I would not want more than one instance to the config class, maintain readability and have easy access to the class. Any ideas?
Note that I'm looking for some best practice or something including a code sample, not some random ideas. Also, if I'm bound to a $this->class->method style pattern, then would I implement this efficiently?
In response to Sébastien Renauld's comments: here's an article on Dependency Injection (DI) and Inversion of Control (IoC) with some examples, and a few extra words on the Hollywood principle (quite important when working on a framework).
Saying your classes won't ever need more than a single instance doesn't mean that statics are a must. Far from it, actually. If you browse this site, and read through PHP questions that deal with the singleton "pattern", you'll soon find out why singletons are a bit of a no-no.
I won't go into the details, but testing and singletons don't mix. Dependency injection is definitely worth a closer look. I'll leave it at that for now.
To answer your question:
Your exaple (Config::get('test')) implies you have a static property in the Config class somewhere. Now if you've done this, as you say, to facilitate access to given data, imagine what a nightmare it would be to debug your code, if that value were to change somewhere... It's a static, so change it once, and it's changed everywhere. Finding out where it was changed might be harder than you anticipated. Even so, that's nothing compared to the issues someone who uses your code will have in the same situation.
And yet, the real problems will only start when that person using your code wants to test whatever it is he/she made: If you want to have access to an instance in a given object, that has been instantiated in some class, there are plenty of ways to do so (especially in a framework):
class Application
{//base class of your framework
private $defaulDB = null;
public $env = null;
public function __construct($env = 'test')
{
$this->env = $env;
}
private function connectDB(PDO $connection = null)
{
if ($connection === null)
{
$connection = new PDO();//you know the deal...
}
$this->defaultDB = $connection;
}
public function getDB(PDO $conn = null)
{//get connection
if ($this->defaultDB === null)
{
$this->connectDB($conn);
}
return $this->defaultDB;
}
public function registerController(MyConstroller $controller)
{//<== magic!
$controller->registerApplication($this);
return $this;
}
}
As you can see, the Application class has a method that passes the Application instance to your controller, or whatever part of your framework you want to grant access to scope of the Application class.
Note that I've declared the defaultDB property as a private property, so I'm using a getter. I can, if I wanted to, pass a connection to that getter. There's a lot more you can do with that connection, of course, but I can't be bothered writing a full framework to show you everything you can do here :).
Basically, all your controllers will extend the MyController class, which could be an abstract class that looks like this:
abstract class MyController
{
private $app = null;
protected $db = null;
public function __construct(Application $app = null)
{
if ($app !== null)
{
return $this->registerApplication($app);
}
}
public function registerApplication(Application $app)
{
$this->app = $app;
return $this;
}
public function getApplication()
{
return $this->app;
}
}
So in your code, you can easily do something along the lines of:
$controller = new MyController($this);//assuming the instance is created in the Application class
$controller = new MyController();
$controller->registerApplication($appInstance);
In both cases, you can get that single DB instance like so:
$controller->getApplication()->getDB();
You can test your framework with easily by passing a different DB connection to the getDB method, if the defaultDB property hasn't been set in this case. With some extra work you can register multiple DB connections at the same time and access those at will, too:
$controller->getApplication->getDB(new PDO());//pass test connection here...
This is, by no means, the full explanation, but I wanted to get this answer in quite quickly before you end up with a huge static (and thus useless) codebase.
In response to comments from OP:
On how I'd tackle the Config class. Honestly, I'd pretty much do the same thing as I'd do with the defaultDB property as shown above. But I'd probably allow for more targeted control on what class gets access to what part of the config:
class Application
{
private $config = null;
public function __construct($env = 'test', $config = null)
{//get default config path or use path passed as argument
$this->config = new Config(parse_ini_file($config));
}
public function registerController(MyController $controller)
{
$controller->setApplication($this);
}
public function registerDB(MyDB $wrapper, $connect = true)
{//assume MyDB is a wrapper class, that gets the connection data from the config
$wrapper->setConfig(new Config($this->config->getSection('DB')));
$this->defaultDB = $wrapper;
return $this;
}
}
class MyController
{
private $app = null;
public function getApplication()
{
return $this->app;
}
public function setApplication(Application $app)
{
$this->app = $app;
return $this;
}
//Optional:
public function getConfig()
{
return $this->app->getConfig();
}
public function getDB()
{
return $this->app->getDB();
}
}
Those last two methods aren't really required, you could just as well write something like:
$controller->getApplication()->getConfig();
Again, this snippet is all a bit messy and incomplete, but it does go to show you that you can "expose" certain properties of one class, by passing a reference to that class to another. Even if the properties are private, you can use getters to access them all the same. You can also use various register-methods to control what it is the registered object is allowed to see, as I've done with the DB-wrapper in my snippet. A DB class shouldn't deal with viewscripts and namespaces, or autoloaders. That's why I'm only registering the DB section of the config.
Basically, a lot of your main components will end up sharing a number of methods. In other words, they'll end up implementing a given interface. For each main component (assuming the classic MVC pattern), you'll have one abstract base-class, and an inheritance chain of 1 or 2 levels of child classes: Abstract Controller > DefaultController > ProjectSpecificController.
At the same time, all of these classes will probably expect another instance to be passed to them when constructed. Just look at the index.php of any ZendFW project:
$application = new Zend_Application(APPLICATION_ENV);
$application->bootstrap()->run();
That's all you can see, but inside the application, all other classes are being instantiated. That's why you can access neigh on everything from anywhere: all classes have been instantiated inside another class along these lines:
public function initController(Request $request)
{
$this->currentController = $request->getController();
$this->currentController = new $this->currentController($this);
return $this->currentController->init($request)
->{$request->getAction().'Action'}();
}
By passing $this to the constructor of a controller class, that class can use various getters and setters to get to whatever it needs... Look at the examples above, it could use getDB, or getConfig and use that data if that's what it needs.
That's how most frameworks I've tinkered or worked with function: The application is kicks into action and determines what needs to be done. That's the Hollywood-principle, or Inversion of Control: the Application is started, and the application determines what classes it needs when. In the link I provided I believe this is compared to a store creating its own customers: the store is built, and decides what it wants to sell. In order to sell it, it will create the clients it wants, and provide them with the means they need to purchase the goods...
And, before I forget: Yes, all this can be done without a single static variable, let alone function, coming into play. I've built my own framework, and I've never felt there was no other way than to "go static". I did use the Factory pattern at first, but ditched it pretty quickly.
IMHO, a good framework is modular: you should be able to use bits of it (like Symfony's components), without issues. Using the Factory pattern makes you assume too much. You assume class X will be available, which isn't a given.
Registering those classes that are available makes for far more portable components. Consider this:
class AssumeFactory
{
private $db = null;
public function getDB(PDO $db = null)
{
if ($db === null)
{
$config = Factory::getConfig();//assumes Config class
$db = new PDO($config->getDBString());
}
$this->db = $db;
return $this->db;
}
}
As opposed to:
class RegisteredApplication
{//assume this is registered to current Application
public function getDB(PDO $fallback = null, $setToApplication = false)
{
if ($this->getApplication()->getDB() === null)
{//defensive
if ($setToApplication === true && $fallback !== null)
{
$this->getApplication()->setDB($fallback);
return $fallback;//this is current connection
}
if ($fallback === null && $this->getApplication()->getConfig() !== null)
{//if DB is not set #app, check config:
$fallback = $this->getApplication()->getConfig()->getSection('DB');
$fallback = new PDO($fallback->connString, $fallback->user, $fallback->pass);
return $fallback;
}
throw new RuntimeException('No DB connection set #app, no fallback');
}
if ($setToApplication === true && $fallback !== null)
{
$this->getApplication()->setDB($fallback);
}
return $this->getApplication()->getDB();
}
}
Though the latter version is slightly more work to write, it's quite clear which of the two is the better bet. The first version just assumes too much, and doesn't allow for safety-nets. It's also quite dictatorial: suppose I've written a test, and I need the results to go to another DB. I therefore need to change the DB connection, for the entire application (user input, errors, stats... they're all likely to be stored in a DB).
For those two reasons alone, the second snippet is the better candidate: I can pass another DB connection, that overwrites the application default, or, if I don't want to do that, I can either use the default connection, or attempt to create the default connection. Store the connection I just made, or not... the choice is entirely mine. If nothing works, I just get a RuntimeException thrown at me, but that's not the point.
Magic methods would help you: see the examples about __get() and __set()
You should also take a look at namespaces: it may help you to get rid of some classes with static methods only.

Switching between controllers (MVC)

I'm trying to understand and figure out a good way to switch between controllers in my custom framework. The following example is what I'm currently thinking, simplified for demonstration purposes, but I would really appreciate some expert advice if there is a better approach?
class BaseController() {
function __construct() {
$this->model = new ModelFactory();
$this->view = new View();
if(isset($_SERVER['QUERY_STRING'])) {
list($controller, $action) = explode('=', $_SERVER['QUERY_STRING']);
self::process($controller);
}
}
public function process($controller) {
switch($controller) {
case 'user':
$user = new UserController($action);
break;
case 'forum':
$forum = new ForumController($action);
break;
default:
// use base controller
switch($action) {
case 'contact':
$this->view->load($action);
break;
}
}
}
}
// inside UserController.php
switch($action) {
case 'register':
break;
case 'login':
break;
}
// inside ForumController.php
switch($action) {
case 'new_thread':
break;
case 'edit_post':
break;
}
This is really a partial answer that will hopefully give you some good pointers. I'm sure someone with a better answer will come along.
Your BaseController in your example is probably misnamed. What you have in it makes it look more like a controller factory than a base controller all other controller classes might derive from. It looks like this is more of a "routing" class, so you should consider giving it a more appropriate name for its job.
If you want your framework users to create custom controllers with custom actions:
a) You'll definitely want to create at least an interface for all controller classes to implement. Call it IController or something like that. This is used in the next step.
b) You'll have to settle for creating objects using strings as classnames. IE $controllerObject = new $controller(); within your "Route" handler class. The reason being is that controller and action names to run come straight from the request URL. There's ways of aliasing this part, but that's another question entirely. Do not forget to validate and/or whitelist these "controller" class names passed in from the client. To validate: use the PHP functions class_exists($controller) and then if true, check to make sure the controller class implements IController using PHP's built-in class_implements($controller). Only then should you do $controllerObject = new $controller(); to actually create the controller object.
Your "Route" process method then becomes something more like (and keep in mind this is a very simplified example):
public function process($controller, $action) {
if (!class_exists($controller)) {
throw new Exception('Controller class does not exist.');
}
if (!in_array("IController", class_implements($controller))) {
throw new Exception('Route is not a valid controller.');
}
if (!method_exists($controller, $action)) {
throw new Exception('No such action for requested controller.');
}
$ctrl = new $controller();
return $ctrl->$action();
}
c) Do not have your controller declare any method (ie named with the value of whatever $action may be) you do not want the client to execute directly using this above design pattern. Hopefully your framework users understand this as well. Just be sure to properly document how this works internally to make your framework users aware.
There is of course way more to it than that, but that's up to you - the framework designer. Also, the action should have the final say in what "view" to use. There's ways to set defaults if the action doesn't explicitly spell out the view to use though. But again, that would be for another question.
You really should start by reading up on what MVC actually is. I would recommend to begin with Fowler's GUI Architectures. Because one thing is quite sure - what you have there isn't it.
It looks like, what you have named BaseController, is actually dealing with routing. It thats the user's request URL and includes the a file based on what you retrieved from said URL. This is neither OOP nor procedural programming. What you have there is known as non-structured programming.
Instead of this madness you should create a separate class(es), that implement routing mechanism for you application. And based on the data, that you extract with said structures, you should initiate specific controller instance, and call a method on it.
Something like:
$request = new Request('QUERY_STRING');
$router = new Router;
$router->import('/path/to/routing/config.file');
$router->route( $request );
$klass = $request->getParameter('controller');
if (class_exists( $controller ))
{
$command = $request->getMethod() . $request->getParameter('action');
}
else
{
$klass = 'Error';
$command = 'getMessage';
}
$controller = new $klass;
$controller->{$command}( $request );
This of course is an extremely simplified version. The MVC pattern is meant to bring some order to large scale projects. If you use it for creating something like a simple blog/business-card website, it would seem like an overkill (assuming that the page does not grow).
P.S. you might find the following links useful for your studies: this, this, this and this.

PHP views relate controllers

I would like to implement controllers that connect to any specific views like MVC does. I'm not using any framework that provided in PHP.
So, I need some guide and advice on doing it.
I have some controllers and views. For my views,i would like to just output my data only.
My concern now is how my function (like create() ) in controllers, can get all the $_POST['params'] that users input data in my views/create.php, and create a new Model in the create() controllers's function.
So,right now, i'm thinking to do in this way, I will create MyViews class in my controllers folder. The purpose is loading the specific views and get all the $_POST params into an object. Then, every controllers like Users_controllers, will create MyViews. In the function of Users_controllers, like create(), destroy(), I might use the function in MyViews to load specific views to load the object.
I found a source that load views
<?php
class MyView {
protected $template_dir = 'templates/';
protected $vars = array();
public function __construct($template_dir = null) {
if ($template_dir !== null) {
// Check here whether this directory really exists
$this->template_dir = $template_dir;
}
}
public function render($template_file) {
if (file_exists($this->template_dir.$template_file)) {
include $this->template_dir.$template_file;
} else {
throw new Exception('no template file ' . $template_file . ' present in directory ' . $this->template_dir);
}
}
public function __set($name, $value) {
$this->vars[$name] = $value;
}
public function __get($name) {
return $this->vars[$name];
}
} ?>
hmm,I have no idea How I can detect the _POST params
if(isset($_POST['Post']))
{
$model->attributes=$_POST['Post'];
if($model->save())
$this->redirect(array('view','id'=>$model->id));
}
this is the Yii framework I observed. How could I detect params whether is $_POST or $_GET after load a specific views.
Any guidance and advice to archive my tasks?
Unrelared to question You have one major problem: your ability to express what mean is extremely limited. The question, which you asked, was actually unrelated to your problem.
From what I gather, you need to detect of user made a POST or GET request. Do detect it directly you can check $_SERVER['REQUEST_METHOD'], but checking it withing controller might be quite bothersome. You will end up with a lot of controller's methods which behave differently based on request method.
Since you are not using any of popular frameworks, is would recommend for you to instead delegate this decision to the routing mechanism.
A pretty good way to handle this, in my opinion, is to prefix the controller's method names with the request method: postLogin(), getArticles() etc. You can find few additional example here. If there is a POST request, it will have something in $_POST array.
What are calling "views" are actually templates. If you read this article, you will notice, that the code there is actually an improved version of your MyView. Views are not templates. Views are instances which contain presentation logic and manipulate multiple templates.
P.S. If you are exploring MVC and MVC-inspired patterns in relation to PHP, you might find this post useful.

The best option for calling model functionality in a PHP controller

I am building a custom MVC framework using PHP. My problem is when I want to access any model class through the controller class. One way I have seen this done is through a registry design pattern using magic methods such as get and set, though PHP get and set are considered bad practise by some. I have read about dependency injection done through a container, but I can not see this working effectily as the container would have to call the models or it would have to contain the models which would defeat the purpose of MVC and create a massive super class. Singleton is seen as bad practise. Is there any solutions or improvements of the methods I have mentioned. It may just be my understand and knowledge of PHP needs improving.
Currently I have this: router.php (loads up controllor through a GET varible
<?php
class router {
function __construct() {
if (file_exists("controller/".$_GET['url']."Controller.php")) {
function __autoload($controller) {
$controlinclude = "controller/".$controller.".php";
include $controlinclude;
}
$control = $_GET['url']."Controller";
new $control();
}
else {
// throw exception
}
}
}
?>
Hope that makes sence
First of all ... Do no put autoloading script in routing mechanism. You are mixing the responsibilities. You will be better off creating a separate class for this based on spl_autoload_register.
Neeext .. do no put complicated operations on constructor. It is makes you code somewhat untestable. Maybe you should be something like:
// you might want to replace $_GET with $_SERVER['QUERY_STRING'] later
$router = new Router( $_GET['url'] );
// where 'default' is the name of fallback controller
$controller_class = $router->get_controller( 'default' );
$method_name = $router->get_action( 'index' );
$model_factory = new ModelFactory( new PDO( ... ) );
$controller = new {$controller_class}( $model_factory );
$controller->{$method_name}();
Additionally, you should look into php namespaces. There is no point in ending class with ...Controller just to know where the class will be located.
Ok ... back to the Model.
There is quite common misconception about models in web development community ( i blame RoR for this mess ). Model in MVC is not a class, but an application layer which contains multitude of instances. Most of the instances belong to one of two types of classes. With following responsibilities:
Domain Logic :
Deals with all the computation, calculation and all the domain specific details. Objects in this group have no knowledge of where and how data is actually stored. They only manipulate the information.
Data Access
Usually made of objects that fit DataMapper pattern (do not confuse with ORM of same name .. nothing in common). Responsible for storing data from Domain Objects and retrieving them. Might be in database.. might not. This is where your SQL queries would be.
In semi-real world situation () it might looks something like this (related to code abowe):
class SomeController
{
// ... snip ...
protected $model_factory = null;
// ... snip ...
public function __construct( ModelFactory $factory )
{
$this->model_factory = $factory;
}
// ... snip ...
public function action_foobar()
{
$user = $this->model_factory->build_object( 'User' );
$mapper = $this->model_factory->build_mapper( 'User' );
$user->set_id(42);
$mapper->fetch($user);
if ( $user->hasWarning() )
{
$user->set_status( 'locked' );
}
$mapper->store( $user );
}
// ... snip ...
}
As you see, there is no indication how the data was stored. It does not even matter if user account was new, or already existing.
Some materials you might find useful
Videos
Advanced OO Patterns (slides)
Clean Code Talks: Don't Look For Things!
Clean Code Talks: Unit Testing
Clean Code Talks: Global State and Singletons
Books:
Real-World Solutions for Developing High-Quality PHP Frameworks and Applications
Patterns of enterprise application architecture
Clean Code: A Handbook of Agile Software Craftsmanship
SQL Antipatterns: Avoiding the Pitfalls of Database Programming
A great Dependency Injection container is "pimple", which may be considered a service locator by some. It uses php 5.3's closures to create a class, that is used to create all of your project's objects through lazy loading. So, for instance, you can create a closure that contains the code for initializing a given object. You would then use the DI container's get() method, which would in turn, call the closure to create the object. Or simply pass you the object, if it has already been created.
// simplified dic class
class dic {
protected $closures = array();
protected $classes = array();
public function addResource($name, Closure $initialization_closure) {
$this->closures[$name] = $initialization_closure;
}
public function get($name) {
if (isset($this->classes[$name]) === false) {
$this->classes[$name] = $this->closures[$name]();
}
return $this->classes[$name];
}
}
//setup
$dic = new dic();
$dic->addResource('user', function() {
return new UserClass($some_args);
});
$dic->addResource('userContainer', function() use ($dic) {
return new UserContainerClass($dic->get('user'));
});
// usage
$userContainer = $dic->get('userContainer');
This allows you to keep the flexibility of being able to change how and what objects get created throughout your project by only changing a very small amount of code.

Categories