Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

404 and router fixes #1352

Closed
wants to merge 13 commits into from

3 participants

@sourcejedi

Don't create the controller twice when using 404_override

That's the main commit anyway. While I was rearranging this, I tried to avoid breaking anything. Since the boundary of what would work as expected was a little ragged, I had to shore it up a bit first :).

2.1.0 tried to make it safe to create the controller twice. However I still noticed a problem when loading models in the constructor of MY_Controller (using db auto-connect). This caused an error "Undefined property: Page_Not_Found::$db" in system/core/Model.php.

I wouldn't be surprised if there were other lurking issues with the same root cause. (E.g. I notice the hooks called "pre_controller" and "post_controller". Those names could be misleading, in the case where the controller was created twice).

sourcejedi added some commits
@sourcejedi sourcejedi Comment fix for the $rsegments field 05acc38
@sourcejedi sourcejedi Remove unused field $error_routes 8f7d2df
@sourcejedi sourcejedi Fix _set_request() to call uri->_reindex_segments()
Each call to _set_request() requires a follow-up call
to uri->_reindex_segments().  It is not optional.
So _set_request() should be calling it itself.

This fixes the following buggy call sequence:

 _set_routing()
   _parse_routes()
     _set_request()
       _set_default_controller()
         _set_request()
         _uri->_reindex_segments()
  _uri->_reindex_segments()

where we end up calling _uri->reindex_segments()
more than once.
922ec05
@sourcejedi sourcejedi Remove _reindex_segments()
_reindex_segments() makes it hard to re-organize the Router code;
we have to make sure to call it only once.  Replace it with a
more sensible _set_rsegments() method.
425fd9f
@sourcejedi sourcejedi Make sure _set_request_novalidate() always sets the method
I.e. it should replace any old value.

(See next previous commit message for an example where this caused
a problem.  In future, this will be useful when a request has to
fall back to the 404_override controller).
3b7d997
@sourcejedi sourcejedi Don't strlower() the 'default_controller' route
strlower() isn't used anywhere else in the router,
so there's no reason it should be needed for
the default route.
708cfe3
@sourcejedi sourcejedi Add _set_404() method to the Router
Much the same as _set_default_controller().
This will be used to unify 404 handling.
045236b
@sourcejedi sourcejedi Fix request validation for the 'enable_query_strings' case
Under 'enable_query_string' we set the directory, class
and method directly.  Don't smush them into an array
and pass them to _validate_request(), because then
it has to guess whether a directory was used or not.

This commit will also preserve method arguments set in
the 404_override route, if we end up using that.
(Instead of silently ignoring them).
ff61081
@sourcejedi sourcejedi Fix _set_default_controller() not to look in sub-folders
_set_default_controller() sets the class and method directly.
That's ok, because we haven't actually defined what it
would mean to have a default route pointing to a sub-folder.
(Does the URL /directory/ cause us to look in a sub-sub-folder?
Could we get into an infinite loop?)

But then it goes ahead and call _set_request(), which may
try to look in sub-folders.  It would then re-set the class
 - and the method.  Unless the default route doesn't specify
a method, in which case it would set a new class but preserve
the old method, producing an inconsistent request.

Split the part of _set_request() we _do_ want into
_set_request_novalidate(), and call that instead.
5a63c68
@sourcejedi sourcejedi Check for missing 404_override controller file in _set_404()
This matches the behaviour of 404_override
in the CodeIgniter bootstrap file
(which triggers e.g. when the _method_ is missing).
3320b66
@sourcejedi sourcejedi is_callable() was fixed in PHP 5_0; remove workaround
The bug with is_callable() and private methods was fixed
some time in PHP 5.0.  We require 5.2.4, so we can switch
back to is_callable().

<http://uk.php.net/manual/en/function.is-callable.php>
<https://bugs.php.net/bug.php?id=29210>
d2de251
@sourcejedi sourcejedi Use the router's _set_404() in CodeIgniter bootstrap
I.e. if the method doesn't exist.  The router already
needs this code, so we don't need to duplicate it here.

This also improves consistency in the segments passed to
the 404_override controller.  (Both $this->uri->rsegments
and any extra function parameters should reflect the
404_override route.  The failed uri is available in e.g.
$this->uri->segments).
ffead8e
@sourcejedi sourcejedi Don't create the controller twice when using 404_override
2.1.0 tried to make it safe to create the controller twice.
However I still noticed a problem when loading models
in the constructor of MY_Controller (using db auto-connect).
This caused an error "Undefined property: Page_Not_Found::$db"
in system/core/Model.php.

I wouldn't be surprised if there were other lurking issues.
(E.g. I notice the hooks called "pre_controller" and "post_controller"
... those names could be misleading in the case where the controller
was created twice).

This also happens to affect #1062
<bcit-ci#1062>
(404_override with _remap() method doesn't work).
_remap() should now get invoked whenever the 404_override
is used, instead of only in some cases.
e7d8435
@philsturgeon philsturgeon commented on the diff
system/core/Loader.php
((6 lines not shown))
*/
public function initialize()
{
- $this->_ci_classes = array();

Whats happening here? Accident?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@narfbg narfbg referenced this pull request from a commit
@narfbg narfbg Router-related optimizations
An improved version of changes suggesed in PR #1352, and more specifically:

sourcejedi@8f7d2df
sourcejedi@d2de251

(thanks again @sourcejedi)
7d394f8
@narfbg
Owner

Implemented (alternatively) most of the proposed changes.

@narfbg narfbg closed this
@sourcejedi

Great! I'm not deeply attached to the exact cleanups I put in. I was just trying to make sure the singleton fix didn't end up breaking anyone else's site.

Thanks for looking at my pull requests.

@nonchip nonchip referenced this pull request from a commit in nonchip/CodeIgniter
@narfbg narfbg Router-related optimizations
An improved version of changes suggesed in PR #1352, and more specifically:

sourcejedi@8f7d2df
sourcejedi@d2de251

(thanks again @sourcejedi)
ac7ee8f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on May 11, 2012
  1. @sourcejedi
  2. @sourcejedi
  3. @sourcejedi

    Fix _set_request() to call uri->_reindex_segments()

    sourcejedi authored
    Each call to _set_request() requires a follow-up call
    to uri->_reindex_segments().  It is not optional.
    So _set_request() should be calling it itself.
    
    This fixes the following buggy call sequence:
    
     _set_routing()
       _parse_routes()
         _set_request()
           _set_default_controller()
             _set_request()
             _uri->_reindex_segments()
      _uri->_reindex_segments()
    
    where we end up calling _uri->reindex_segments()
    more than once.
  4. @sourcejedi

    Remove _reindex_segments()

    sourcejedi authored
    _reindex_segments() makes it hard to re-organize the Router code;
    we have to make sure to call it only once.  Replace it with a
    more sensible _set_rsegments() method.
  5. @sourcejedi

    Make sure _set_request_novalidate() always sets the method

    sourcejedi authored
    I.e. it should replace any old value.
    
    (See next previous commit message for an example where this caused
    a problem.  In future, this will be useful when a request has to
    fall back to the 404_override controller).
  6. @sourcejedi

    Don't strlower() the 'default_controller' route

    sourcejedi authored
    strlower() isn't used anywhere else in the router,
    so there's no reason it should be needed for
    the default route.
  7. @sourcejedi

    Add _set_404() method to the Router

    sourcejedi authored
    Much the same as _set_default_controller().
    This will be used to unify 404 handling.
  8. @sourcejedi

    Fix request validation for the 'enable_query_strings' case

    sourcejedi authored
    Under 'enable_query_string' we set the directory, class
    and method directly.  Don't smush them into an array
    and pass them to _validate_request(), because then
    it has to guess whether a directory was used or not.
    
    This commit will also preserve method arguments set in
    the 404_override route, if we end up using that.
    (Instead of silently ignoring them).
  9. @sourcejedi

    Fix _set_default_controller() not to look in sub-folders

    sourcejedi authored
    _set_default_controller() sets the class and method directly.
    That's ok, because we haven't actually defined what it
    would mean to have a default route pointing to a sub-folder.
    (Does the URL /directory/ cause us to look in a sub-sub-folder?
    Could we get into an infinite loop?)
    
    But then it goes ahead and call _set_request(), which may
    try to look in sub-folders.  It would then re-set the class
     - and the method.  Unless the default route doesn't specify
    a method, in which case it would set a new class but preserve
    the old method, producing an inconsistent request.
    
    Split the part of _set_request() we _do_ want into
    _set_request_novalidate(), and call that instead.
  10. @sourcejedi

    Check for missing 404_override controller file in _set_404()

    sourcejedi authored
    This matches the behaviour of 404_override
    in the CodeIgniter bootstrap file
    (which triggers e.g. when the _method_ is missing).
  11. @sourcejedi

    is_callable() was fixed in PHP 5_0; remove workaround

    sourcejedi authored
    The bug with is_callable() and private methods was fixed
    some time in PHP 5.0.  We require 5.2.4, so we can switch
    back to is_callable().
    
    <http://uk.php.net/manual/en/function.is-callable.php>
    <https://bugs.php.net/bug.php?id=29210>
  12. @sourcejedi

    Use the router's _set_404() in CodeIgniter bootstrap

    sourcejedi authored
    I.e. if the method doesn't exist.  The router already
    needs this code, so we don't need to duplicate it here.
    
    This also improves consistency in the segments passed to
    the 404_override controller.  (Both $this->uri->rsegments
    and any extra function parameters should reflect the
    404_override route.  The failed uri is available in e.g.
    $this->uri->segments).
  13. @sourcejedi

    Don't create the controller twice when using 404_override

    sourcejedi authored
    2.1.0 tried to make it safe to create the controller twice.
    However I still noticed a problem when loading models
    in the constructor of MY_Controller (using db auto-connect).
    This caused an error "Undefined property: Page_Not_Found::$db"
    in system/core/Model.php.
    
    I wouldn't be surprised if there were other lurking issues.
    (E.g. I notice the hooks called "pre_controller" and "post_controller"
    ... those names could be misleading in the case where the controller
    was created twice).
    
    This also happens to affect #1062
    <bcit-ci#1062>
    (404_override with _remap() method doesn't work).
    _remap() should now get invoked whenever the 404_override
    is used, instead of only in some cases.
This page is out of date. Refresh to see the latest.
View
65 system/core/CodeIgniter.php
@@ -273,27 +273,28 @@ function &get_instance()
if ( ! class_exists($class)
OR strpos($method, '_') === 0
- OR in_array(strtolower($method), array_map('strtolower', get_class_methods('CI_Controller')))
+ OR is_callable(array('CI_Controller', $method))
)
{
- if ( ! empty($RTR->routes['404_override']))
- {
- $x = explode('/', $RTR->routes['404_override'], 2);
- $class = $x[0];
- $method = isset($x[1]) ? $x[1] : 'index';
- if ( ! class_exists($class))
- {
- if ( ! file_exists(APPPATH.'controllers/'.$class.'.php'))
- {
- show_404($class.'/'.$method);
- }
-
- include_once(APPPATH.'controllers/'.$class.'.php');
- }
- }
- else
+ $RTR->_set_404($class.'/'.$method);
+ $class = $RTR->fetch_class();
+ $method = $RTR->fetch_method();
+ include_once(APPPATH.'controllers/'.$class.'.php');
+ }
+
+/*
+ * ------------------------------------------------------
+ * Check the requested method exists
+ * ------------------------------------------------------
+ */
+ if ( ! method_exists($class, '_remap'))
+ {
+ if ( ! is_callable(array($class, $method)))
{
- show_404($class.'/'.$method);
+ $RTR->_set_404($class.'/'.$method);
+ $class = $RTR->fetch_class();
+ $method = $RTR->fetch_method();
+ include_once(APPPATH.'controllers/'.$RTR->fetch_directory().$class.'.php');
}
}
@@ -333,34 +334,6 @@ function &get_instance()
}
else
{
- // is_callable() returns TRUE on some versions of PHP 5 for private and protected
- // methods, so we'll use this workaround for consistent behavior
- if ( ! in_array(strtolower($method), array_map('strtolower', get_class_methods($CI))))
- {
- // Check and see if we are using a 404 override and use it.
- if ( ! empty($RTR->routes['404_override']))
- {
- $x = explode('/', $RTR->routes['404_override'], 2);
- $class = $x[0];
- $method = isset($x[1]) ? $x[1] : 'index';
- if ( ! class_exists($class))
- {
- if ( ! file_exists(APPPATH.'controllers/'.$class.'.php'))
- {
- show_404($class.'/'.$method);
- }
-
- include_once(APPPATH.'controllers/'.$class.'.php');
- unset($CI);
- $CI = new $class();
- }
- }
- else
- {
- show_404($class.'/'.$method);
- }
- }
-
// Call the requested method.
// Any URI segments present (besides the class/function) will be passed to the method for convenience
call_user_func_array(array(&$CI, $method), array_slice($URI->rsegments, 2));
View
7 system/core/Loader.php
@@ -149,17 +149,12 @@ public function __construct()
*
* This method is called once in CI_Controller.
*
- * @return object
+ * @return void
*/
public function initialize()
{
- $this->_ci_classes = array();

Whats happening here? Accident?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
- $this->_ci_loaded_files = array();
- $this->_ci_models = array();
$this->_base_classes =& is_loaded();
-
$this->_ci_autoloader();
- return $this;
}
// --------------------------------------------------------------------
View
138 system/core/Router.php
@@ -53,13 +53,6 @@ class CI_Router {
public $routes = array();
/**
- * List of error routes
- *
- * @var array
- */
- public $error_routes = array();
-
- /**
* Current class name
*
* @var string
@@ -86,7 +79,14 @@ class CI_Router {
* @var string
*/
public $default_controller;
-
+
+ /**
+ * Controller to show for 404 errors (and method if specific)
+ *
+ * @var string
+ */
+ public $controller_404;
+
/**
* Constructor
*
@@ -117,22 +117,16 @@ public function _set_routing()
$segments = array();
if ($this->config->item('enable_query_strings') === TRUE && isset($_GET[$this->config->item('controller_trigger')]))
{
- if (isset($_GET[$this->config->item('directory_trigger')]))
- {
- $this->set_directory(trim($this->uri->_filter_uri($_GET[$this->config->item('directory_trigger')])));
- $segments[] = $this->fetch_directory();
- }
+ $this->set_class(trim($this->uri->_filter_uri($_GET[$this->config->item('controller_trigger')])));
- if (isset($_GET[$this->config->item('controller_trigger')]))
+ if (isset($_GET[$this->config->item('function_trigger')]))
{
- $this->set_class(trim($this->uri->_filter_uri($_GET[$this->config->item('controller_trigger')])));
- $segments[] = $this->fetch_class();
+ $this->set_method(trim($this->uri->_filter_uri($_GET[$this->config->item('function_trigger')])));
}
- if (isset($_GET[$this->config->item('function_trigger')]))
+ if (isset($_GET[$this->config->item('directory_trigger')]))
{
- $this->set_method(trim($this->uri->_filter_uri($_GET[$this->config->item('function_trigger')])));
- $segments[] = $this->fetch_method();
+ $this->set_directory(trim($this->uri->_filter_uri($_GET[$this->config->item('directory_trigger')])));
}
}
@@ -149,14 +143,23 @@ public function _set_routing()
$this->routes = ( ! isset($route) OR ! is_array($route)) ? array() : $route;
unset($route);
- // Set the default controller so we can display it in the event
- // the URI doesn't correlated to a valid controller.
- $this->default_controller = empty($this->routes['default_controller']) ? FALSE : strtolower($this->routes['default_controller']);
+ // Set the default controller used for the empty URI, or
+ // if the URI matches only a directory. (Think "index page").
+ $this->default_controller = empty($this->routes['default_controller']) ? FALSE : $this->routes['default_controller'];
+
+ // Set the 404 controller so we can display it in the event
+ // the URI doesn't match a valid controller.
+ $this->controller_404 = empty($this->routes['404_override']) ? FALSE : $this->routes['404_override'];
// Were there any query string segments? If so, we'll validate them and bail out since we're done.
- if (count($segments) > 0)
+ if ($this->class != '')
{
- return $this->_validate_request($segments);
+ if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$this->class.'.php'))
+ {
+ $this->_set_404($this->fetch_directory().$this->class);
+ }
+
+ return;
}
// Fetch the complete URI string
@@ -171,7 +174,6 @@ public function _set_routing()
$this->uri->_remove_url_suffix(); // Remove the URL suffix
$this->uri->_explode_segments(); // Compile the segments into an array
$this->_parse_routes(); // Parse any custom routing that may exist
- $this->uri->_reindex_segments(); // Re-index the segment array so that it starts with 1 rather than 0
}
// --------------------------------------------------------------------
@@ -187,25 +189,42 @@ protected function _set_default_controller()
{
show_error('Unable to determine what should be displayed. A default route has not been specified in the routing file.');
}
- // Is the method being specified?
- if (strpos($this->default_controller, '/') !== FALSE)
+ $segments = explode('/', $this->default_controller);
+
+ if ( ! file_exists(APPPATH.'controllers/'.$segments[0].'.php'))
{
- $x = explode('/', $this->default_controller);
- $this->set_class($x[0]);
- $this->set_method($x[1]);
- $this->_set_request($x);
+ show_404($segments[0]);
}
- else
+ $this->_set_request_novalidate($segments);
+
+ log_message('debug', 'No URI present. Default controller set.');
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * Set the 404_override controller
+ *
+ * If there is no 404_override controller,
+ * this function will abort by calling show_404().
+ *
+ * @param string
+ * @return void
+ */
+ public function _set_404($page)
+ {
+ if ($this->controller_404 === FALSE)
{
- $this->set_class($this->default_controller);
- $this->set_method('index');
- $this->_set_request(array($this->default_controller, 'index'));
+ show_404($page);
}
- // re-index the routed segments array so it starts with 1 rather than 0
- $this->uri->_reindex_segments();
+ $segments = explode('/', $this->controller_404);
- log_message('debug', 'No URI present. Default controller set.');
+ if ( ! file_exists(APPPATH.'controllers/'.$segments[0].'.php'))
+ {
+ show_404($segments[0]);
+ }
+ $this->_set_request_novalidate($segments);
}
// --------------------------------------------------------------------
@@ -214,7 +233,8 @@ protected function _set_default_controller()
* Set the Route
*
* This function takes an array of URI segments as
- * input, and sets the current class/method
+ * input, sets the current class/method, and makes
+ * sure we know how to find the class.
*
* @param array
* @return void
@@ -228,24 +248,35 @@ protected function _set_request($segments = array())
return $this->_set_default_controller();
}
+ $this->_set_request_novalidate($segments);
+ }
+
+ // --------------------------------------------------------------------
+
+ /**
+ * Set the Route
+ *
+ * This function takes an array of URI segments as
+ * input, and sets the current class/method
+ *
+ * @param array
+ * @return void
+ */
+ protected function _set_request_novalidate($segments)
+ {
$this->set_class($segments[0]);
- if (isset($segments[1]))
+ if ( ! isset($segments[1]))
{
- // A standard method request
- $this->set_method($segments[1]);
- }
- else
- {
- // This lets the "routed" segment array identify that the default
- // index method is being used.
+ // Use the default index method
$segments[1] = 'index';
}
+ $this->set_method($segments[1]);
// Update our "routed" segment array to contain the segments.
- // Note: If there is no custom routing, this array will be
+ // Note: If there is no custom routing, the array will be
// identical to $this->uri->segments
- $this->uri->rsegments = $segments;
+ $this->uri->_set_rsegments($segments);
}
// --------------------------------------------------------------------
@@ -353,8 +384,13 @@ protected function _validate_request($segments)
*/
protected function _parse_routes()
{
+ // $uri->segments array starts at 1.
+ // Copy it to an array starting at 0,
+ // so we can do normal array manipulation.
+ $segments = array_values($this->uri->segments);
+
// Turn the segment array into a URI string
- $uri = implode('/', $this->uri->segments);
+ $uri = implode('/', $segments);
// Is there a literal match? If so we're done
if (isset($this->routes[$uri]))
@@ -383,7 +419,7 @@ protected function _parse_routes()
// If we got this far it means we didn't encounter a
// matching route so we'll set the site default route
- $this->_set_request($this->uri->segments);
+ $this->_set_request($segments);
}
// --------------------------------------------------------------------
View
25 system/core/URI.php
@@ -59,8 +59,7 @@ class CI_URI {
public $segments = array();
/**
- * Re-indexed list of uri segments
- * Starts at 1 instead of 0
+ * List of routed uri segments
*
* @var array
*/
@@ -324,27 +323,31 @@ public function _explode_segments()
$this->segments[] = $val;
}
}
+
+ // We re-index the $this->segments array so that it
+ // starts at 1 rather than 0. Doing so makes it simpler to
+ // use functions like $this->uri->segment(n) since there is
+ // a 1:1 relationship between the segment array and the actual segments.
+ array_unshift($this->segments, NULL);
+ unset($this->segments[0]);
}
// --------------------------------------------------------------------
/**
- * Re-index Segments
- *
- * This function re-indexes the $this->segment array so that it
- * starts at 1 rather than 0. Doing so makes it simpler to
- * use functions like $this->uri->segment(n) since there is
- * a 1:1 relationship between the segment array and the actual segments.
+ * Set the routed URI Segments
*
* Called by CI_Router
*
* @return void
*/
- public function _reindex_segments()
+ public function _set_rsegments($segments)
{
- array_unshift($this->segments, NULL);
+ // Take a copy of the array, so we don't re-index the caller's copy
+ $this->rsegments = array_values($segments);
+
+ // re-index to match $this->segments
array_unshift($this->rsegments, NULL);
- unset($this->segments[0]);
unset($this->rsegments[0]);
}
View
2  tests/codeigniter/core/URI_test.php
@@ -120,7 +120,7 @@ public function test_explode_segments()
$this->uri->uri_string = $uri;
$this->uri->_explode_segments();
- $b = $this->uri->segments;
+ $b = array_values($this->uri->segments);
$this->assertEquals($a, $b);
}
Something went wrong with that request. Please try again.