I am trying to learn MVC and i want to to display a store page when the url is www.example.com/store and another cart page wenn the url is www.example.com/store/cart. Both methods are inside one class:
class store extends controller {
function __construct() {
parent::__construct ();
}
function Store() { //this should display the store page
$this->view->render ( "store" );
}
function cart() { //this should display the cart page
$this->view->render ( "store", "cart.php" );
}
}
the method $this->view->render() includes the pages with the html code.
How can I decide which method to execute based on the url?
I can see you are trying to write your own MVC so I will try to explain things you should be aware of while doing this.
First thing you want to do while writing MVC is utilize Front Controller pattern.
What this means is that every HTTP Request will go through one file, index.php.
This will help you to cofigure your application in one file. You can do this by just always opening your lets say:
www.example.com/index.php/controller/method/param1/param2
or you can force users to go through index.php using .htaccess file.
For reference check other frameworks like Codeigniter and CakePHP, see how they use .htaccess files.
When you are in your front controller file, you should think about routing your HTTP Request to appropriate Controller / Method. There are really lots of ways to achive that, and its up to you to figure out the best way to do that.
Anyway you need Router class that will analyse your URL, and extract Controller / Method / Params you need from URL. Then when you know what Controller / Method you need to invoke, you need Dispatcher class that would instantiate Controller, and call Method you need. You should also think about Params you want to pass to your Method so you can use them there.
The easiest way to play around with this is to use query strings.
So lets say you have URL like this:
http://localhost/test/index.php?controller=home&method=welcome
You can easily get Controller and Method names.
Now you can do something like this:
// Get controller and method names
$controller = $_GET['controller'];
$method = $_GET['method'];
// Instantiate controller object
$app = new $controller();
// Call method on you controller object
call_user_func_array(array($app, $method));
This is like the simplest thing you can do just to play around.
Once you get hold of this, write your Router class so it can look like this:
http://localhost/test/index.php/controller/method/param1/param2
Anyway you need these classes:
Front Controller so every request goes through one file, and this is where you bootstrap you application, register class autoloading, configure everything etc...
This is not a class but a file with procedural code that boots everything up.
Router that will analyse URL and give you back Controller, Method, and Params that you want to pass to invoked Method.
Dispatcher that will instantiate Controller and call Method passing Params to your Method.
And then all control goes to user of your system, then you do your magic inside your controller / method.
While Dispatching request you can also use this:
call_user_func_array(array($app, $method), $params);
this way you are passing $params to $method, so you can use $params inside your method.
Also, check out Reflection class, this can help you to analyse if method you want to invoke exists, if not you should call HTTP 404 etc...
There is no simple answere to your question, but I hope this helps,
have fun building your system and just improve until you get it perfect :)
You need to have a separate Router class which does the routes requests to the controller. I suggest looking at/copying Symfony's Router.
I once did something like this for a home brew approach.
Use .htaccess (apache) / Web.config (iis) to redirect all requests to the following script.
// include callable controllers here
$partsA = explode("?", $_SERVER['REQUEST_URI']); // split querystring
$partsB = explode("/", $partsA[0]); // get url parts
if (count($partsB) < 2)
die("missing controller in url");
elseif (count($partsB) < 3)
die("missing action in url");
$className = $partsB[1];
$methodName = $partsB[2];
if (class_exists($className))
if (!is_subclass_of($className, "controller"))
die(htmlspecialchars("Class $className doesn't extend controller")); // prevents use of unauthorized classes
else
$controller = new $className();
else
die(htmlspecialchars("Class $className doesn't exist"));
if (!method_exists($controller, $methodName))
die(htmlspecialchars("Method $methodName doesn't exist"));
else
$controller->$methodName();
Related
I am a Java developer (I often used Spring MVC to develop MVC web app in Java) with a very litle knowledge of PHP and I have to work on a PHP project that use CodeIgniter 2.1.3.
So I have some doubts about how controller works in CodeIgniter.
1) In Spring MVC I have a controller class with some annoted method, each method handle a specific HTTP Request (the annotation defines the URL handled by the method) and return the name of the view that have to be shown.
Reading the official documentation of CodeIgniter it seems me that the logic of this framework is pretty different: https://www.codeigniter.com/userguide3/general/controllers.html#what-is-a-controller
So it seems to understand that in CodeIgniter is a class that handle a single URL of the application having the same name of the class name. Is it correct?
So I have this class:
class garanzieValoreFlex extends CI_Controller {
.....................................................
.....................................................
.....................................................
function __construct() {
parent::__construct();
$this->load->helper(array('form', 'url'));
$this->load->library(array('form_validation','session'));
}
public function reset() {
$this->session->unset_userdata("datiPreventivo");
$this->load->view('garanziavalore/garanzie_valore_questionario_bootstrap',array());
}
public function index() {
$this->load->model('Direct');
$flagDeroga = "true" ;
$this->session->userdata("flagDeroga");
$data = $this->session->userdata("datiPreventivo");
$this->load->model('GaranzieValoreFlexModel');
$data = $this->session->userdata("datiPreventivo");
$this->load->model('GaranzieValoreFlexModel');
$this->load->view('garanziavalore/index_bootstrap',$data);
}
public function back() {
$this->load->model('Direct');
$flagDeroga = "true" ;
$this->session->userdata("flagDeroga");
$data = $this->session->userdata("datiPreventivo");
$this->load->model('GaranzieValoreFlexModel');
//$this->load->view('garanziavalore/garanzie_valore_questionario_bootstrap',$data);
$this->load->view('garanziavalore/index_tornaIndietro_bootstrap',$data);
}
.....................................................
.....................................................
.....................................................
}
So, from what I have understand, basically this controller handle only the HTTP Request toward the URL: http://MYURL/garanzieValoreFlex.
So from what I have understand the method performed when I access to the previous URL is the index() that by this line:
$this->load->view('garanziavalore/index_bootstrap',$data);
show the garanziavalore/index_bootstrap.php page that I found into the views directory of my prohect (is it a standard that have to be into the views directory?)
Is it my reasoning correct?
If yes I am loading the view passing to id also the $data variable that I think is the model containing the data that can be shown in the page, this variable is retrieved by:
$data = $this->session->userdata("datiPreventivo");
What exactly does this line?
The last doubt is related the other back() method that I have found in the previous controller: is it a method of CodeIgniter CI_Controller class or something totally custom defined by the developer that work on this application before me?
A controller can handle more than one URL and the class garanzieValoreFlex is an example of such a class.
The URL http://MYURL/garanzieValoreFlex will call the index method.
The URLs http://MYURL/garanzieValoreFlex/back and http://MYURL/garanzieValoreFlex/reset will call the back() and reset() methods of the class respectively. These two function are custom additions to the extended class CI_Controller.
Codeigniter (CI) URLs follow the pattern example.com/class/function/argument/
The function and argument segments are optional.
When a URL uses only a class name such as example.com/class then CI will look for and call the index() method if it exists. If index() does not exist you will get 404 Page Not Found display.
Your reasoning about $this->load->view('garanziavalore/index_bootstrap',$data); is correct. It is standard to put such files in the views directory. Optionally, subdirectories of views can be used as in /views/garanziavalore/.
CI uses a file structure that associates different classes (libraries) with certain paths. Controllers, Models and View classes are stored in their respective folders. Then the loader class will know exactly where to start looking for any given "type" of class. For instance, the call to $this->load->view('garanziavalore/index_bootstrap',$data); tells the loader class to get the file index_boostrap.php from the /application/views/garanzivalore/ directory. The code $this->load->model('GaranzieValoreFlexModel'); tells the loader to use the file GaranzieValoreFlexModel.php in /application/models/.
Find documentation of the loader class here.
The line of code
$data = $this->session->userdata("datiPreventivo");
is calling the userdata method of the session class (library). Think of session data as an array. If the array was defined this way. (This is only pseudo-code for what is accomplished).
$userdata = array(); //empty array structure
The call $this->session->userdata("datiPreventivo") is in effect returning the value of $userdata["datiPreventivo"].
Your reasoning is wrong. I would really recommend you to read the official codeigniter tutorials so that you may understand how the MVC works:
Below are the links
Codeigniter 2:
http://www.codeigniter.com/userguide2/
Codeigniter 3:
http://www.codeigniter.com/user_guide/
CI controllers handles different urls. If you create a function called index in a controller, it will be loaded automatically when the controller is accessed. For your case, http://MYURL/garanzieValoreFlex should access the function.
To access any other function, you will need to http://MYURL/garanzieValoreFlex/**MyFunction**
(Read more http://www.codeigniter.com/user_guide/general/urls.html?highlight=url#codeigniter-urls) The back function is a user defined function.
Since my app is getting bigger i would like to separate admin prefix actions and views from normal actions and views. The new folder for admin is Controller/admin/UsersController.php.
I would like to change my cakephp controllers and views folder structure to match the prefix I'm using.
Example for admin prefix:
Controller:
app/Controller/UsersController.php (contain view(), index() ...)
app/Controller/admin/UsersController.php (contain admin_view(), admin_index() ...)
View:
app/View/Users/index.ctp (for index() in UsersController.php)
app/View/Users/admin/index.ctp (for admin_index() in admin/UsersController.php)
How can I implement this structure using Cakephp 2.6?
Unlike in 3.x where this is the default behavior for prefixes, this isn't supported in 2.x.
You could try hacking it in using a custom/extended dispatcher (in order to retrieve the desired controller) or even dispatcher filters in case you are adventurous, and in your app controller modify the view template path with respect to the prefix.
That should do it, however I would probably simply go for using plugins instead, this will separate things just fine without any additional fiddling.
If you just want to separate logic you could do something like this. It's untested an just thought to give you just the idea. I'll explain the concept after the code:
public function beforeFilter() {
if ($this->request->prefix === 'foo') {
$name = Inflector::classify($this->request->prefix);
$className = $name . 'ChildController';
App::uses($className, 'Controller/Foo');
$this->ChildController = new $className($this);
}
}
public function __call($method, $args) {
if ($this->request->prefix === 'foo' && method_exists($this->ChildController, $method)) {
call_user_func_array([$this->ChildController, $method], $args);
}
}
Depending on the prefix you can load other classes. How you load that class and how you instantiate it, what params you pass to it is up to you. In my example I'm passing the controller instance directly. I think you could actually init a complete controller here but be aware that components like the Session might cause a problem because they might have been already initiated by the "parent" controller.
When you now call a controller method that doesn't exist it will try to call the same method with the same arguments on the ChildController. Not really a great name for it, but maybe you can come up with something better.
You'll have to implement some logic to load the views from the right place in your classes as well but this shouldn't be hard, just check the controller class.
But actually I don't see your problem, I've worked on an app that hat over 560 tables and not putting the code into sub folders wasn't a problem, it did in fact use a similar solution.
I think you have to much code in your controllers, get more code into your models and the controller shouldn't be a problem.
Another solution might be to think about implementing a service layer in CakePHP which implements the actual business logic while a model is reduced to a data handler. The service would sit between a controller and the model. I've done this a few times as well now and if done right it works very well.
I am working on code someone wrote based on Fat-Free Framework. Basically its a CRM.
I have seen that he uses a dispatch function like the one i here:
What is the difference between URL Router and Dispatcher?
I have not found enough documentation on this approach.
Anyway he also put the authentication in the dispatcher like this:
function dispatch()
{
if(UserManager::isLogin())
{
$controller = $router->getController();
$actionName = $router->getAction();
$controller[$actionName]();
}
else
{
routeTo('/login');
}
}
My question is: Do you think it is correct to put one centralized authentication check inside the dispatcher for all the controllers or would you do a log in check inside each controller, or would you do something else? I appropriate examples from well known framework or CMS.
Thanks
First F3 does not allow dispatching by default so the approach is to use the beforeroute() function.
In your base controller(or class routed to) add
public function beforeroute($f3){
if(!UserManager::isLogin())
{
$f3->reroute('/login');
}
}
Then any time you call the class (which extends your base class with the above function), it first validates the login. Hope this helps.
I am having a controller IndexController.php in which action is something like this
class IndexController extends CustomControllerAction {
public function preDispatch() {
if (!$this->view->authenticated) {
$this->_redirect('/users/login');
}
}
public function indexemailAction() {
//somecode which calculates certain things
}
}
NOw,I need to call the action "indexmailAction" inside the IndexController.php with an independent php file
The php file is indextest.php
<?php
//Need to write some code to call indexmailAction in IndexController.php
?>
What should I write in this file ......
Thanks in advance
I know this is a few years old, and this may not be the intended use of the classes/functions, but I've found the following quite useful in isolated files that are called from the command line.
The problem this solves for me is that it eliminates spawning of Apache processes. The solution is great because I can access the some Controller/Action needed that I would from the URL.
In almost any ZF1 based app, you can copy your index file and keep everything the same and just comment out the following line.
$application->run();
Anything below this line you can access with your autoloaders etc. It's crude, but it works. Unfortunately, you'll soon find yourself with limited access to a lot of the files your application has, and the feeling the only way you can access the files needed is through a Controller/Action.
Instead, I use the following in a new file below $application->bootstrap() ( still removing the $application->run() ):
$front = Zend_Controller_Front::getInstance();
// You can put more here if you use non-default modules
$front->setControllerDirectory(array(
'default' => APPLICATION_PATH.'/controllers'
));
$viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer();
$viewRenderer->setNeverRender(true);
Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);
$req = new Zend_Controller_Request_Http("http://anydomain.tld/controller/action");
// Example just to see how this can be extended
$req->setParam("someVar", "someValue");
$front->setRequest($req);
$front->dispatch();
In the end you have a isolated PHP file that bootstraps everything the same as your main index.php for the web, but you can manually trigger a controller/action as needed, giving you easier access to the rest of the files with how ZF1 intended you to access them.
Controllers are designed to be used in an MVC, not by scripts. Your controller should assemble request variables, direct them to models and return an HTTP response of some sort. Your scripts should act directly on the models instead.
Anyhow, if you insist, you can instantiate a controller class and call methods just like any other class as long as you inject any dependencies that the MVC would have.
If you want logic used in multiple places in your actions, then it should go in an action helper or if very generic code, then in a custom library (/library/custom/)
NB: The authentication would be better suited in a plugin rather than the pre-dispatch method in every controller.
You should not have to call a controller action for this, your logic should reside in your models. Then you can create a new instance of your model and invoke the appropriate methods. example :
require_once '/path/to/mymodel.php';
$mymodel = new Mymodel();
$data = $mymodele->fetchAll();
PS: Maybe you should think of creating a restful api to handle calls from outside your application
UPDATE:
ok, I see now what you need, The best way to achieve it is to call a url instead of a file (e.g. website.com/emails/send), if you are worried about security you can use some preshared key to make sure the request comes from you, send it with the request and check if it's correct in your action.
This might be a bit hard to comprehend so I apologize in advance if this is not clear enough.
I'm writing my own MVC framework and am once again stuck.
I am in the process of writing the controller classes for the framework. Basically this is how it works:
Instantiate class coreController which extends abstract class
coreController sets controller to be loaded by interpreting query string
query string values stored in variables
other variables assigned values
new controller is loaded
new controller checks if an action object needs to be instantiated.
new actioncontroller is loaded
action controller checks if it is the final object required.
action controller is returned as an object to be referenced during the rest of the script.
generic $controller->method() can be called and references final controller loaded.
Another overview:
coreController
pageController
pageControllerActionAdd
return as object to start
$controller->something(); //References pageControllerActionAdd
Esentially what I want to be able to do is be able enter a url like:
http://www.mywebsite.com/page/modify/
and have the script pull up the PageModifyController as a variable so I can execute it's methods.
If you can tell me a better method for what I am doing please go ahead. You don't have to write any code, just the idea would be great. It is just that the way I am currently doing is very confusing and hard to debug. I will end up with multiple nested objects and I don't like the concept of that.
I've been reading a lot of other source code and found that it too can be quite sophisticated.
I actually created a framework that works along the lines you are trying to implement. I think what you are missing is a RoutingHandler class. Routing is the physical manipulation of the URL, which tells your application which Controller to load, and which Action to run.
In my world I also have Modules, so the basic routing scheme is
Module -> Controller -> Action
These three items map to my URI scheme in that fashion. Variables can be appended also like so...
http://www.domain.com/module/controller/action/var1/val1/var2/val2
So, what happens after the URI is parsed, and control is passed over to the appropriate controller and action? Let's make some code up to demonstrate a simple example...
<?php
class indexController extends Controller {
protected function Initialize() {
$this->objHomeModel = new HomeModel;
$this->objHeader = new Header();
$this->objFooter = new Footer();
$this->objHeader
->SetPageId('home');
}
public function indexAction() {
$this->objHeader->SetPageTitle('This is my page title.');
}
// other actions and/or helper methods...
}
?>
In the Initialize method, I'm setting some controller-wide stuff, and grabbing an instance of my Model to use later. The real meat is in the indexAction method. This is where you would set up stuff to use in your View. For example...
public function randomAction() {
$this->_CONTROL->Append($intSomeVar, 42);
}
_CONTROL is an array of values that I manipulate and pass onto the View. The Controller class knows how to find the right template for the View because it is named after the Action (and in a sibling directory).
The Controller parent class takes the name of the action method and parses it like so...
indexAction -> index.tpl.php
You can also do some other fun stuff here, for example...
Application::SetNoRender();
...would tell the Controller not to render inside a template, but just complete the method. This is useful for those situations where you don't actually want to output anything.
Lastly, all of the controllers, models, and views live inside their own Module directory like so...
my_module
controllers
indexController.class.php
someotherController.class.php
:
:
models
HomeModel.class.php
:
:
templates
index.tpl.php
someother.tpl.php
:
:
I can have as many Modules as I need, which means I can separate functionality out by Module and/or Controller.
I could go on, but I'm writing this from memory, and there are some wrinkles here and there, but hopefully this gives you food for thought.