EllisLab text mark
Advanced Search
     
Multi-level subfolders for controllers in CI 2.x
Posted: 01 June 2011 04:23 PM
Joined: 2010-11-18
61 posts

I’ve seen several posts on here that talk about how to handle multi-level subfolders in CI 2.x but no one has posted the complete code.  I found this post: http://ellislab.com/forums/viewthread/129469/, but the complete solution was not posted.  Could someone please post the entire script, with detailed instructions on how to implement.  I couldn’t sort out how to make the changes from the 1.7.1 code.

Thanks.
Scott

 
Posted: 03 June 2011 01:52 AM   [ # 1 ]   [ Rating: 0 ]
Joined: 2009-09-17
81 posts

Well, here you go sehummel: yet another multi-level subfolders controller extension.  This was for 2.0.0.  No changes in the affected methods in 2.0.2, so should work there as well.

This will support things like the following:
(Note that ‘default_controller’ is as defined in ‘config/routers.php’)

If you have the following controllers:
controllers/user/<default_controller>.php
controllers/user/profile.php

Then:
http://www.myhost.com/user/ maps to http://www.myhost.com/user/<default_controller&gt;
http://www.myhost.com/user/prefs maps to http://www.myhost.com/user/<default_controller>/prefs
http://www.myhost.com/user/profile maps to http://www.myhost.com/user/profile

I think there is a “bug” in CI 2+ where ‘show_404()’ will error because all the core classes are not loaded yet, hence it was not use in this code.

HTH, Damien K.

<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
 * MY_Router Class
 *
 * @author        Damien K.
 */
class MY_Router extends CI_Router
{
    
// --------------------------------------------------------------------

    /**
     * OVERRIDE
     *
     * Validates the supplied segments.  Attempts to determine the path to
     * the controller.
     *
     * @access    private
     * @param    array
     * @return    array
     */
    
function _validate_request($segments)
    
{
        
if (count($segments) == 0)
        
{
            
return $segments;
        
}

        
// Does the requested controller exist in the root folder?
        
if (file_exists(APPPATH.'controllers/'.$segments[0].EXT))
        
{
            
return $segments;
        
}

        
// Is the controller in a sub-folder?
        
if (is_dir(APPPATH.'controllers/'.$segments[0]))
        
{
            
// @edit: Support multi-level sub-folders
            
$dir '';
            do
            
{
                
if (strlen($dir) > 0)
                
{
                    $dir 
.= '/';
                
}
                $dir 
.= $segments[0];
                
$segments array_slice($segments1);
            
while (count($segments) > && is_dir(APPPATH.'controllers/'.$dir.'/'.$segments[0]));
            
// Set the directory and remove it from the segment array
            
$this->set_directory($dir);
            
// @edit: END

            // @edit: If no controller found, use 'default_controller' as defined in 'config/routes.php'
            
if (count($segments) > && ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$segments[0].EXT))
            
{
                array_unshift
($segments$this->default_controller);
            
}
            
// @edit: END

            
if (count($segments) > 0)
            
{
                
// Does the requested controller exist in the sub-folder?
                
if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$segments[0].EXT))
                
{
                    
// show_404($this->fetch_directory().$segments[0]);
                    // @edit: Fix a "bug" where show_404 is called before all the core classes are loaded
                    
$this->directory '';
                    
// @edit: END
                
}
            }
            
else
            
{
                
// Is the method being specified in the route?
                
if (strpos($this->default_controller'/') !== FALSE)
                
{
                    $x 
explode('/'$this->default_controller);

                    
$this->set_class($x[0]);
                    
$this->set_method($x[1]);
                
}
                
else
                
{
                    $this
->set_class($this->default_controller);
                    
$this->set_method('index');
                
}

                
// Does the default controller exist in the sub-folder?
                
if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$this->default_controller.EXT))
                
{
                    $this
->directory '';
                    return array();
                
}

            }

            
return $segments;
        
}


        
// If we've gotten this far it means that the URI does not correlate to a valid
        // controller class.  We will now see if there is an override
        
if (!empty($this->routes['404_override']))
        
{
            $x 
explode('/'$this->routes['404_override']);

            
$this->set_class($x[0]);
            
$this->set_method(isset($x[1]) ? $x[1] 'index');

            return 
$x;
        
}


        
// Nothing else to do at this point but show a 404
        
show_404($segments[0]);
    
}

    
// --------------------------------------------------------------------

    /**
     * OVERRIDE
     *
     * Set the directory name
     *
     * @access    public
     * @param    string
     * @return    void
     */
    
function set_directory($dir)
    
{
        $this
->directory str_replace(array('.'), ''$dir).'/'// @edit: Preserve '/'
    
}

 Signature 

Damien K.

 
Posted: 03 June 2011 05:25 PM   [ # 2 ]   [ Rating: 0 ]
Joined: 2010-11-18
61 posts

This is great, Damien, but I can’t seem to get it working.  I placed the code in a file called MY_router.php in the application/libraries folder and uploaded it.  I have a controller called markets that I put in the test folder in the cms folder (controllers/cms/test/markets.php). I uploaded this folder structure and even built a route for it.  But I get a 404 error. 

My subclass prefix is set to the default of “MY_” so that’s not the problem.  Any idea off hand what I might be doing wrong?  Will your router file work for the folder structure I have?  It looks like it from your post, but I wasn’t quite sure.

I really appreciate your help with this!

 
Posted: 03 June 2011 06:22 PM   [ # 3 ]   [ Rating: 0 ]
Avatar
Joined: 2008-11-06
317 posts

MY_router.php should be put into application/core folder

 
Posted: 05 June 2011 06:14 AM   [ # 4 ]   [ Rating: 0 ]
Joined: 2011-04-12
37 posts

this solution was useful like for me, I tried for days ...
thanks

 
Posted: 05 June 2011 06:34 AM   [ # 5 ]   [ Rating: 0 ]
Joined: 2011-04-12
37 posts

How could I extend your solution also to the models folder and views folder?

Thanks

 
Posted: 06 June 2011 10:06 AM   [ # 6 ]   [ Rating: 0 ]
Joined: 2009-09-17
81 posts

@sehummel Like what cideveloper said.

 Signature 

Damien K.

 
Posted: 06 June 2011 10:08 AM   [ # 7 ]   [ Rating: 0 ]
Joined: 2009-09-17
81 posts

@gbar I think multi-level folders/directories already works for models and views.

$this->load->model(‘shopping/international/exchange_model’);
$this->load->view(‘shopping/cart/favourite’);

 Signature 

Damien K.

 
Posted: 06 June 2011 10:19 AM   [ # 8 ]   [ Rating: 0 ]
Joined: 2011-04-12
37 posts

I worked on this thing all day yesterday, making a lot of testing ...
is true, a structure so it works:

$this->load->model(‘shopping/international/exchange_model’);
$this->load->view(‘shopping/cart/favourite’); 

but it is painfully slow, at least in my experience I had ...
This morning I reported levels of depth views and models in a single folder

$this->load->model(‘folder/model’); 
$this->load->view(‘folder/form_view’); 

and the application has reacquired the optimal speed, while your solution for controllers, it works really well.

 
Posted: 29 November 2011 05:11 AM   [ # 9 ]   [ Rating: 0 ]
Joined: 2010-09-06
3 posts

This does not seem to work when you want to use parts of the URI as query strings

like so: /sub/dir/my_controller/argument1/argument2

I would like this to call /sub/dir/my_controller->index(argument1, argument2)

but it does not work.

I need to put the index in the URL explicitly: /sub/dir/my_controller/index/argument1/argument2

 
Posted: 24 January 2012 08:04 PM   [ # 10 ]   [ Rating: 0 ]
Avatar
Joined: 2012-01-24
41 posts

I came up with something nifty that works for me, feel free to try it out.

In your applications core library if you don’t already have one add a subclass of CI_Loader and override the view function like so.

class MY_Loader extends CI_Loader {


    
function view($view$vars = array(), $return FALSE)
     
{
       $seg 
explode('<>'$view);
       
$out '';
       
$ct 0;
       If(
$seg != FALSE{
         
foreach($seg as $dir{
           
switch($dir{
            
case 'f':
              
$out .= 'forms/';
              break;
            case 
't':
              
$out .= 'template/';
              break;
            case 
'r':
              
$out .= 'registration/';
              break;
           
}
           $ct
++;
         
}
         $view 
$out $seg[($ct 1)];
       
}
             
return $this->_ci_load(array('_ci_view' => $view'_ci_vars' => $this->_ci_object_to_array($vars), '_ci_return' => $return));
     
}

To make this function work for your needs however you will have to add case statements for the sub-folders that you wish to abbreviate so to speak.

Lets say in your views you have a folder named content you would just add the following code to the switch statement.

case 'c':
              
$out .= 'content/';
              break; 

The resulting view calls to this folder can now be done using the following load call

$this->load->view('c<>some_content'); 

This will also work if you have multi-level sub-folders like this

$this->load->view('f<>r<>some_view'); 

This would have the same effect as doing this.

$this->load->view('forms/registration/some_view'); 


I find it helps code readability and also results in less typing when you wish to load views out of sub-folders.  Keep in mind this addition will still retain the normal functionality if you load the view with its full path as well.

I just thought this might be something you may want to look at.  I enjoy using it so hope someone else does too.

Cheers

 Signature 

Excellence is an art won by training and habituation. We do not act rightly because we have virtue or excellence, but we rather have those because we have acted rightly. We are what we repeatedly do. Excellence, then, is not an act but a habit.

~Aristotle

 
Posted: 24 January 2012 11:46 PM   [ # 11 ]   [ Rating: 0 ]
Avatar
Joined: 2012-01-24
41 posts

I came up with an alternative to Damien k.‘s method below after I coded my view example above.

Here it will only throw a loop into my added code if it detects a URI depth that is above 2, otherwise it proceeds as it normally would.

Give it a try.

protected function _validate_request($segments)
 
{
  
if (count($segments) === 0)
  
{
   
return $segments;
  
}

  
// Does the requested controller exist in the root folder?
  
if (file_exists(APPPATH.'controllers/'.$segments[0].'.php'))
  
{
   
return $segments;
  
}

  
// Is the controller in a sub-folder?
  
if (is_dir(APPPATH.'controllers/'.$segments[0]))
  
{
   
// Set the directory and remove it from the segment array
   
$this->set_directory($segments[0]);

                        
$segCnt count($segments);
   
$segments array_slice($segments1);
                      if(
$segCnt 2{
                        $this
->DoSubFold TRUE;

                        for(
$ct 2$ct <  $segCnt$ct++) {
                              
if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$segments[0].EXT)) {
                                  $this
->set_directory($this->fetch_directory() . $segments[0]);

                                if(  
$ct == $segCnt{
                                    show_404
($this->fetch_directory().$segments[0]);
                                

                                
                                  $segments 
array_slice($segments1);
                                
                                
                                
                              
}
                        }
                        
return $segments;

                      
}

   
if (count($segments) > 0)
   
{
    
// Does the requested controller exist in the sub-folder?
    
if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$segments[0].'.php'))
    
{
     
if ( ! empty($this->routes['404_override']))
     
{
      $x 
explode('/'$this->routes['404_override']);
      
$this->set_directory('');
      
$this->set_class($x[0]);
      
$this->set_method(isset($x[1]) ? $x[1] 'index');

      return 
$x;
     
}
     
else
     
{
      show_404
($this->fetch_directory().$segments[0]);
     
}
    }
   }
   
else
   
{
    
// Is the method being specified in the route?
    
if (strpos($this->default_controller'/') !== FALSE)
    
{
     $x 
explode('/'$this->default_controller);
     
$this->set_class($x[0]);
     
$this->set_method($x[1]);
    
}
    
else
    
{
     $this
->set_class($this->default_controller);
     
$this->set_method('index');
    
}

    
// Does the default controller exist in the sub-folder?
    
if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$this->default_controller.'.php'))
    
{
     $this
->directory '';
     return array();
    
}

   }

   
return $segments;
  
}


  
// If we've gotten this far it means that the URI does not correlate to a valid
  // controller class. We will now see if there is an override
  
if ( ! empty($this->routes['404_override']))
  
{
   $x 
explode('/'$this->routes['404_override']);
   
$this->set_class($x[0]);
   
$this->set_method(isset($x[1]) ? $x[1] 'index');

   return 
$x;
  
}

  
// Nothing else to do at this point but show a 404
  
show_404($segments[0]);
 
}

 
public function set_directory($dir)
 
{
          
if(!$this->DoSubFold{
  $this
->directory str_replace(array('/''.'), ''$dir).'/';
          
}
          
else
          
{
              $this
->directory str_replace(array('.'), ''$dir) . '/';
          
}
 } 
 Signature 

Excellence is an art won by training and habituation. We do not act rightly because we have virtue or excellence, but we rather have those because we have acted rightly. We are what we repeatedly do. Excellence, then, is not an act but a habit.

~Aristotle

 
Posted: 27 December 2012 03:53 AM   [ # 12 ]   [ Rating: 0 ]
Joined: 2012-12-27
1 posts
nizzle - 29 November 2011 05:11 AM

This does not seem to work when you want to use parts of the URI as query strings

like so: /sub/dir/my_controller/argument1/argument2

I would like this to call /sub/dir/my_controller->index(argument1, argument2)

but it does not work.

I need to put the index in the URL explicitly: /sub/dir/my_controller/index/argument1/argument2

I found a solution about this issue.

You should probably set this below code into your ‘application/config/routes.php’

$route[‘sub/dir/my_controller/(:any)/(:any)’] = ‘sub/dir/my_controller/index/$1/$2’;


Hope this can help anyone.

 
Posted: 28 February 2013 07:05 AM   [ # 13 ]   [ Rating: 0 ]
Joined: 2011-05-11
235 posts

There is a bug in here!

If I use core\MY_router and try to change $route[‘404_override’] = ‘my_error_folder/error’; won’t work together. Either use MY_router or $route[‘404_override’].

Redirecting all errors to a controller works if we don’t use this route extension class however this time we cannot use multi level for controllers!!!

Any solution?