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.
Related
I found the following code. Is this a specific pattern or what could be the reason to structure code like that - or is it just bogus?
class ExportCSV extends Export
{
// some private and public vars
public function __construct($arg)
{
// [...]
new CustomerReport($this);
}
public function procCallback($proc)
{
switch($proc){
case "customer":
new InvoiceReport($this);
break;
case "invoice":
new PrepaymentReport($this);
break;
case "prepayment":
new RefundReport($this);
break;
case "refund":
$this->sendMail();
break;
}
}
}
class CustomerReport extends Foobar
{
private $inst;
public function __construct($inst)
{
$this->inst = $inst;
$this->exportCustomers($inst->from, $inst->to);
}
public function __destruct()
{
$this->inst->procCallback("customer");
}
}
As raina77ow said, is an implementation of a pattern. Besides, you must to consider what do you want to do once the object is destroyed in you application life cycle. Let's consider the following example (please, it is just an example!)
Let's suppose you are trying to implement an MVC pattern and you are the guy who should make the "View part". So what do you need? you need grab all the variables generated in the request and once they're ready to be used in the response (thru the controllers and Models) they should be rendered into the views. One approach (among others, of course) is the implementation of this pattern (Observer) through the magic method __destruct(). For example something like this:
// your code here
public function __destruct() {
$this->grabAllTheVarsAndRenderThem();
// or you can include the views file
extract($this->viewParams);
include_once('my_file_view.php');
}
This is just an example, and btw, is very verbosed (as you can see in the method name). But the idea behind the example is, bind some behavior before the object is destroyed.
Of course there are a lot of situations where you can -and you should- implement this pattern, this is just an example to explain the sense to use this magic method.
Hope it helps!
I wouldn't call the shown code 'Logic' in destructor: it's actually an implementation of Observer pattern.
I assume here it works like that: first, when a CustomerReport object is created, its constructor will register some observing objects (probably with $this->exportCustomers method, which body is not shown here for some reasons) in its $inst field. Then these observing objects will be notified each time this object's state is changed. And, of course, destruction of this object can be viewed as changing of its state too. )
It could be that the original developer of this part of the code doesn't trust the users (that could be the same person too (-: ) of these libraries and want to make sure that every CustomerReport created will do call the collaborator objects (they could be releasing some lock or other critical resource).
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.
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.
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
Currently, I use an abstract factory to allow the specification of a custom class name for generating a request object. My reasoning for doing this is to allow me to easily extend core functionality without altering code. Recently, though, I've had some doubts as to the efficacy of this approach. So my question is this:
Is allowing the factory to instantiate any submitted class name that
matches the expected interface a bastardization of the factory concept?
Would I be better served to avoid this?
UPDATE
The logic here is this: on the one hand, a real-life car factory (for example) can't create a car if it's not equipped with the machinery to make that kind of car. On the other hand, the code below is like giving that same car factory the blueprint to make the custom car it wasn't originally intended to build.
An alternative would be to pass in a configuration object specifying a custom class name that may be used with the factory and limit the factory to producing a custom class only if it specifically matches the config-specified custom class name. Any thoughts?
And the relevant code ...
<?php
interface AbstractRequestFactory
{
public function buildRequest($type);
}
class RequestFactory implements AbstractRequestFactory
{
public function buildRequest($type='http')
{
if ($type == 'http') {
return new HttpRequest();
} elseif ($type == 'cli') {
return new CliRequest();
} elseif ($custom = $this->makeCustom($type)){
return $custom;
} else {
throw new Exception("Invalid request type: $type");
}
}
protected function makeCustom($type)
{
if (class_exists($type, FALSE)) {
$custom = new $type;
return $custom instanceof RequestInterface ? $custom : FALSE;
} else {
return FALSE;
}
}
}
// so using the factory to create a custom request would look like this:
class SpecialRequest implements RequestInterface {}
$factory = new RequestFactory();
$request = $factory->buildRequest('\SpecialRequest');
What you have looks pretty good. The point of having a factory is to pass in some criteria, and have the method return you back an object, which you assume will have the same callable methods available to the calling code. You are enforcing this assumption by implementing the RequestInterface, so as long as any custom request classes implement the same interface, you won't end up in a 'unable to call function on non-object' scenario.
A couple of recommendations (just personal preference):
I would use switch / case on $type in buildRequest
I would return null or object from makeCustom(), otherwise you are mixing return types (object and bool)
Depending on how many custom types you have, I would actually hard code them into the switch case, just to alleviate any confusion. Don't get me wrong, what you have is great if you have a lot of classes, but chances are you don't.
Did you ever consider putting the "easily extend core functionality without altering code" piece into an abstract parent class, which can be extended by custom type classes?
Also, because a factory creates objects, it's common practice to set it as static.
Example code snippet:
public static function getRequest($type='http')
{
switch ($type) {
case 'http':
return new HttpRequest();
case 'cli':
return new CliRequest();
case 'myCustom1':
return new MyCustom1();
case 'myCustom2':
return new MyCustom2();
default:
throw new Exception("Invalid request type: $type");
}
}
$request = RequestFactory::getRequest($type);
// As long as all objects in factory have access to same methods
$request->doSomething();
$request->andDoSomethingElse();
// Otherwise you end up with that feared 'unable to call function on non-object'
$request->iAmASneakyMethodNotEnforcedByAnInterfaceOrAvailableByExtension();
This is pretty subjective, so the following is just one opinion:
I wouldn't be quick to use something like this. If you only have a handful of classes that the factory will ever care about, then I'd just hard code them. But if you have a large set of these, I think it can be appropriate.
Given that you are validating that the class extends the appropriate interface, I would say that there is nothing wrong with what you are doing because it is fail-safe. The code using that factory method will appear clean; I think that's the most important thing.
If you were making use of such techniques all over the place, then I would argue against it. But since this is hidden away in the implementation, I think you can have more leeway with doing slightly inappropriate things.
Why not use a dispatch array? i.e.
class RequestFactory
{
private static $requests = array(
'http' => 'HttpRequest',
'cli' => 'CliRequest',
'summatelse' => 'Summat'
);
public static GetRequest($type)
{
if (array_key_exists($type, $requests)) return new $requests[$type];
else throw new Exception("Invalid request type: $type");
}
}