I have followed this nice video tutorial to create a mini MVC PHP framework.
As you can see, the guy creates an action called home() inside the Controller class to show the main page.
Now, I need to create another action called foobar() that shows another page, but how can i associated it to an url in a simple way?
This is usually done by routing. If the first part of the URL identifies the controller and the second the action, you extract those variables and route accordingly.
A very basic example:
$action = 'foobar';
$controllerName = 'whatever';
if(class_exists($controllerName)){
$controller = new $controllerName;
if(method_exists($controller, $action)){
$controller->$action();
}
}
Obviously, $action and $controllerName are obtained from the URL.
This code snippet tries to call the method 'foobar' inside a class named 'whatever'.
Related
Can I get the controller action from given URL?
In my project, I will have different layout used for admin and normal users. i.e.
something.com/content/list - will show layout 1.
something.com/admin/content/list - will show layout 2.
(But these need to be generated by the same controller)
I have added filter to detect the pattern 'admin/*' for this purpose. Now I need to call the action required by the rest of the URL ('content/list' or anything that will appear there). Meaning, there could be anything after admin/ it could be foo/1/edit (in which case foo controller should be called) or it could be bar/1/edit (in which case bar controller should be called). That is why the controller name should be generated dynamically from the url that the filter captures,
So, I want to get the controller action from the URL (content/list) and then call that controller action from inside the filter.
Can this be done?
Thanks to everyone who participated.
I just found the solution to my problem in another thread. HERE
This is what I did.
if(Request::is('admin/*')) {
$my_route = str_replace(URL::to('admin'),"",Request::url());
$request = Request::create($my_route);
return Route::dispatch($request)->getContent();
}
I could not find these methods in the documentation. So I hope, this will help others too.
You can use Request::segment(index) to get part/segment of the url
// http://www.somedomain.com/somecontroller/someaction/param1/param2
$controller = Request::segment(1); // somecontroller
$action = Request::segment(2); // someaction
$param1 = Request::segment(3); // param1
$param2 = Request::segment(3); // param2
Use this in your controller function -
if (Request::is('admin/*'))
{
//layout for admin (layout 2)
}else{
//normal layout (layout 1)
}
You can use RESTful Controller
Route:controller('/', 'Namespace\yourController');
But the method have to be prefixed by HTTP verb and I am not sure whether it can contain more url segment, in your case, I suggest just use:
Route::group(array('prefix' => 'admin'), function()
{
//map certain path to certain controller, and just throw 404 if no matching route
//it's good practice
Route::('content/list', 'yourController#yourMethod');
});
I am currently looking into the PHP Framework Codeigniter and understand the main concepts so far until the Controllers section about _remapping. I have a understanding how _remapping overwrites the behaviour of controller methods over the URI eg from www.example.com/about_me to www.example.com/about-me. What i would like to hear is peoples opinions on what to use- _remapping method or the URI Routing method? Im only asking this as when researching these methods and someone has been troubled on remapping functions, they have been directed to use URI Routing.
So..
1) What is the main common method to use and the pro's over the other one?
2) Is it best to use URI Routing for PHP5 CI version 2 onwards?
I would be grateful to hear your opinions!
Assuming you do not want to use the index (i.e. http://www.yourdomain.com/category) action of your Categories controller, you can use routes.
$route['category/(:any)'] = 'category/view/$1';
Then you simply need a View action within your Category controller to receive the Category name, i.e. PHP.
http://www.yourdomain.com/category/PHP
function View($Tag)
{
var_dump($Tag);
}
Should you still want to access your index action within your controller, you can still access it via http://www.yourdomain.com/category/index
You should use _remap if you want to change the behaviour of default CI's routing.
For example, if you set a maintenance and want to block any specific controller from running, you can use a _remap() function loading your view, and which will NOT call any other method.
Another example is when you have multiple methods in your URI. Example:
site.com/category/PHP
site.com/category/Javascript
site.com/category/ActionScript
Your controller is category but methods are unlimited.
There you can use a _remap method as called by Colin Williams here:
http://codeigniter.com/forums/viewthread/135187/
function _remap($method)
{
$param_offset = 2;
// Default to index
if ( ! method_exists($this, $method))
{
// We need one more param
$param_offset = 1;
$method = 'index';
}
// Since all we get is $method, load up everything else in the URI
$params = array_slice($this->uri->rsegment_array(), $param_offset);
// Call the determined method with all params
call_user_func_array(array($this, $method), $params);
}
To sum up, if the current CI's routing suits to your project, don't use a _remap() method.
$default_controller = "Home";
$language_alias = array('gr','fr');
$controller_exceptions = array('signup');
$route['default_controller'] = $default_controller;
$route["^(".implode('|', $language_alias).")/(".implode('|', $controller_exceptions).")(.*)"] = '$2';
$route["^(".implode('|', $language_alias).")?/(.*)"] = $default_controller.'/$2';
$route["^((?!\b".implode('\b|\b', $controller_exceptions)."\b).*)$"] = $default_controller.'/$1';
foreach($language_alias as $language)
$route[$language] = $default_controller.'/index';
This is my Zend Framework directory structure (only included what you'll need):
-Controllers
-HomeController.php
-IndexController.php
-Views
-scripts
-home
-index.phtml
-index
-index.phtml
In the IndexController I want to automatically redirect them to the home view.
How do I achieve this?
This is what I have tried so far:
header('/application/views/home');
I want to redirect to a different view and controller altogether. Not a different action within the same controller.
you have some options:
use the _redirect action utility method (very common)
public function indexAction() {
$this->_redirect('/application/views/home');
}
or use the _forward action utility method to just execute the requested action without actually redirecting
public function indexAction() {
$this->_forward('index', 'home');
}
you could use the action helper redirector, similar to _redirect but has many more options.
public function indexAction() {
$this->getHelper('Redirector')->gotoSimple('index','home');
}
This about covers the basics of redirecting inside of a controller, if you want to actually render home/index.phtml from /idex/index you could use the viewrenderer action helper.
I'll let you unlock those secrets.
If you want to render another action inside current action it is possible within your action the following way:
$this->render('actionName');
or, explicitly set the template to render
$this->renderScript('path/to/viewscript.phtml');
UPDATE: Reading again your question I suppose you want to use
_forward($action, $controller = null, $module = null, array $params = null): perform another action. If called in preDispatch(), the currently requested action will be skipped in favor of the new one. Otherwise, after the current action is processed, the action requested in _forward() will be executed.
or
_redirect($url, array $options = array()): redirect to another location. This method takes a URL and an optional set of options. By default, it performs an HTTP 302 redirect.
see documentation on this question.
I have a site that has a lot of pages that lye at the root (ex. /contact, /about, /home, /faq, /privacy, /tos, etc.). My question is should these all be separate controllers or one controller with many methods (ex. contact, about, index within a main.php controller )?
UPDATE:
I just realized that methods that are within the default controller don't show in the url without the default controller (ie. main/contact wont automatically route to /contact if main is the default controller ). So you would need to go into routes and override each page.
If all of these are just pages, I would recommend putting them into a single controller. I usually end up putting static pages like this into a 'pages' controller and putting in routes for each static page to bypass the '/pages' in my URLs.
If they are share the same functionality, so they should be in the same controller.
for example, if all of them are using the same model to take content from, so, one controller can easily handle it.
Why in one controller? because you always want to reuse your code.
class someController{
function cotact(){
print $this->getContentFromModel(1);
}
function about(){
print $this->getContentFromModel(2);
}
function home(){
print $this->getContentFromModel(3);
}
private function getContentFromModel($id){
return $this->someContentModel->getContentById($id);
}
}
(instead of print, you should use load a view)
See in my example how all of the function are using the same getContentFromModel function to share the same functionality.
but this is one case only, there could be ther cases that my example can be bad for...
in application/config/routes.php
$route['contact'] = "mainController/contact";
$route['about'] = "mainController/about";
$route['home'] = "mainController/home";
$route['faq'] = "mainController/faq";
$route['privacy'] = "mainController/privacy";
and you should add all of these methods within the mainController.php
You can also save the content of the pages in your database, and them query it. For instance, you can send the url as the keyword to identify the page content
$route['contact'] = "mainController/getContent/contact";
$route['about'] = "mainController/getContent/about";
$route['home'] = "mainController/getContent/home";
$route['faq'] = "mainController/getContent/faq";
$route['privacy'] = "mainController/getContent/privacy";
in this case you only have to create one method named "getContent" in the controller "mainController" and this method will look something like this:
class mainController extends CI_Controller
{
public function getContent($param)
{
$query = $this->db->get_where('mytable', array('pageName' => $param));
// then get the result and print it in a view
}
}
Hope this works for you
The page names you listed should probably be different methods inside your main controller. When you have other functionality that is related to another specific entity, like user, you can create another controller for the user entity and have different methods to display the user, update the user, register the user. But its all really a tool for you to organize your application in a way that makes sense for your domain and your domain model.
I've written a blog post about organizing CodeIgniter controller methods that might be helpful to you. Check it out here: http://caseyflynn.com/2011/10/26/codeigniter-php-framework-how-to-organize-controllers-to-achieve-dry-principles/
I have started to learn Zend Framework. I have done the quickstart tutorial and the akrabat tutorial.
Now I am trying to work on some of my own project with Zend Framework and it is a frustrating experience to say the least.
I made a layout that creates my standard header that shows up on every page. Now I want to display the user's name in the header which is retrieved from a mysql database. I don't know how to go about it. Do I interact with a controller and model from the layout??
Also, are there any other good guides for this kind of information? The zend documentation seems to be very detailed for each component but not very good at explaining how things work together.
Within your layout you will need this
<?= $this->layout()->content ?>
This will output anything from your view script.
In your project structure you need a controller and a view script for that controller. You should know how to access this if you have done the tutorial.
Your controller should get the username from the DB and assign it to the view like so
$this->view->username = "John";
Then in your view
echo $this->username;
EDIT
In your Bootstrap class register a new plugin
$plugin = new Default_Controller_Plugin_Username();
Zend_Controller_Front::getInstance()->registerPlugin($plugin);
This plugin might look like this
class Default_Controller_Plugin_Username
extends Zend_Controller_Plugin_Abstract
implements Lib_Observer_Observable
{
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
//Do things in here to get username
//Then you can set it in the registry
Zend_Registry::set("username", $username);
}
}
If you have the $user from some kind of auth/login sequence, then you might be using Zend_Auth. The default container for the identity data is session-based, so depending upon your session-handling settings, you might not need to hit the db each time to get that user info.
The bigger question to me is whether you need a front controller plugin to place this $user info from Zend_Auth into the view (to be rendered in your layout) or whether the layout can pull from Zend_Auth directly.
I've seen (and used) both.
Your problem is the problem of the block composition on the page. As you may know Zend Framework is only a Framework, so there's really more than one way to deal with this problem.
To fill the layout part you could for example use :
some variable containing the block and given to the view for each controller (with a controller plugin? a main Controller that all controllers inherits?
the Action Helper in the view to chain an internal call to another MVC call in this part of the layout
the Action stack
certainly others things
I think the nicest one is the Action stack. The way it work is:
You make a main query on the MVC-thing (where you get yout fooAction called in a controller)
You call the Action stack in this Action to tell ZF that some others internal calls on some other Action should be done. Here you can list all blocks that should be filled in the Layout
Others bloacks are called by the internal MVC loop. Some other barAction userAction entry points are called, in these actions you tell the Layout which key of the layout is filled by the resulting view rendering with setResponseSegment.
Here's how it looks in code:
For the block action (here a /modulefoo/titi/pilili internal request), that should fill the 'foobar' block of the Layout :
public function pilipiliAction() {
(...) // do things
$this->_helper->viewRenderer->setResponseSegment('foobar');
}
On the layout you echo this block with:
<?= $this->foobar ?>
Now this action must be called by your main action (with the Action Stack helper). But the nicest way will be to use a new custom helper that will stack all the blocks actions.
Here 's a fooAction on a controller, your classical entry point. At the end of the action an helper is called that should build the blocks of the layouts
public function fooAction() {
(...) // do things
// Build Main layout
$this->_helper->LayoutBuilder();
}
Here's an example of what this helper would do (should work, it's a simplified version of an existing one which makes a lot of others things, like managing cache for blocks):
class My_Action_Helper_LayoutBuilder extends Zend_Controller_Action_Helper_Abstract
{
/**
* #var $actionStack
* local ActionStack builtin helper used to Stack several MVC actions,
* for action for each Layout block
*/
protected static $actionStack;
/**
* #var _layout
* reference to the general layout collecting view
*/
protected $_layout;
public function __construct() {
self::$actionStack = Zend_Controller_Action_HelperBroker::getStaticHelper('actionStack');
$this->_layout = Zend_Layout::getMvcInstance();
}
public function direct($blocklist = null) {
$layoutblocks = array(
array('module' => 'default',
'controller' => 'index',
'action' => 'nav')
,
array('module' => 'modulefoo',
'controller' => 'titi',
'action' => 'pilipili') // action given upper
);
$auth = Zend_Auth::getInstance();
if ($auth->hasIdentity()) {
$request = Zend_Registry::get('request');
$module = $request->getModuleName();
$layoutblocks[] = array('module' => $module,
'controller' => 'index',
'action' => 'modulenav');
}
if (isset($blocklist))
{ //user had his own blocklist to add
$layoutblocks = array_merge($layoutblocks,$blocklist);
}
$this->buildCompositeLayout($layoutblocks);
}
public function buildCompositeLayout($blocklist = null) {
foreach($blocklist as $MVCBlock) {
$block = $MVCBlock['action'];
if (!isset($MVCBlock['module'])) {
$module = $this->getRequest()->getModuleName();
$front = $this->getFrontController();
$module = $front->getDispatcher()->getDefaultModule();
} else {
$module = $MVCBlock['module'];
}
if (!isset($MVCBlock['controller'])) {
if (!isset($front)) {
$front = $this->getFrontController();
}
$controller = $front->getDispatcher()->getDefaultController();
} else {
$controller = $MVCBlock['controller'];
}
// Here we stack actions
self::$actionStack->actionToStack($block,
$controller,
$module,
);
}
}
But as I said before, there's more than one way to do it. I like this way as I can interactively add new blocks for some requests, add some caching in the block rendering, and I can as well 'forget' to call the LayoutBuilder helper for some actions, like the ajax ones. The secret is that the MVC loop of Zend Framework is really a loop; it can run several actions for one request.
Zend_Auth rocks.. you need to use it.
There's a good tutorial on Zend_Auth here:
http://akrabat.com/zend-auth-tutorial/
This includes the process of creating a View Helper to do the repetitive task of displaying the Username, role and a logout link.
Take the time to come to grips with View Helpers.. they really explode your productivity and code reuse when coding views.
Duncan.
#Mike, this answer from #jakenoble is exactly correct for your scenario.
If you want to perform some global actions in your controllers then i suggest to create your own base controller class inherited from Zend_Controller say MyBaseController.
Then you can inherit all your controllers from MyBaseController, this way all the actions performed in init() method of MyBaseController will global to your complete project.