Why Codeignitor does not accept autoload Controller classes when validating routes?

Viewed 121

Why does Codeignitor not accept Controller in composer autoload when validating routes?

It's checking by: class_exists($class, FALSE) where the second parameter disables checking in autoload.

https://github.com/bcit-ci/CodeIgniter

    $e404 = FALSE;
    $class = ucfirst($RTR->class);
    $method = $RTR->method;

    if (empty($class) OR ! file_exists(APPPATH.'controllers/'.$RTR->directory.$class.'.php'))
    {
        $e404 = TRUE;
    }
    else
    {
        require_once(APPPATH.'controllers/'.$RTR->directory.$class.'.php');

        if ( ! class_exists($class, FALSE) OR $method[0] === '_' OR method_exists('CI_Controller', $method))
        {
            $e404 = TRUE;
        }
        elseif (method_exists($class, '_remap'))
        {
            $params = array($method, array_slice($URI->rsegments, 2));
            $method = '_remap';
        }
        elseif ( ! method_exists($class, $method))
        {
            $e404 = TRUE;
        }
        /**
         * DO NOT CHANGE THIS, NOTHING ELSE WORKS!
         *
         * - method_exists() returns true for non-public methods, which passes the previous elseif
         * - is_callable() returns false for PHP 4-style constructors, even if there's a __construct()
         * - method_exists($class, '__construct') won't work because CI_Controller::__construct() is inherited
         * - People will only complain if this doesn't work, even though it is documented that it shouldn't.
         *
         * ReflectionMethod::isConstructor() is the ONLY reliable check,
         * knowing which method will be executed as a constructor.
         */
        elseif ( ! is_callable(array($class, $method)))
        {
            $reflection = new ReflectionMethod($class, $method);
            if ( ! $reflection->isPublic() OR $reflection->isConstructor())
            {
                $e404 = TRUE;
            }
        }
    }
1 Answers

Looking over the git history, the change was introduced in 49e68de96b420a444c826995746a5f09470e76d9, with the commit message being:

Disable autoloader call from class_exists() occurences to improve performance

Note: The Driver libary tests seem to depend on that, so one occurence in CI_Loader is left until we resolve that.

So the nominal reason is performance.

If you want to ensure that the controller classes will be loaded on each request, you can add the files explicitly to the Composer autoload.files attribute, like so:

composer.json
{
    "autoload": {
        "files": [
            "src/Foo.php"
        ]
    },
    "name": "test/64166739"
}
src/Foo.php
<?php
class Foo {}
test.php
<?php
$loader = require('./vendor/autoload.php');
var_dump(class_exists('Foo', false));

When run (via php test.php for example), we get the following output:

bool(true)

Additional

Looking over the code around that call to class_exists, it would appear that the controller files should follow a convention such that, for example with the built in Welcome controller and the default settings, the file that defines it should exist at:

application/controllers/Welcome.php

and so after require_onceing that file, the call to class_exists is a reasonably simple sanity check to ensure that the file did in fact define that class. So, based on this assumption about how controllers are added to the CodeIgniter application (ie all in the application/controllers directory and named the same as the class that they define), it's reasonable to bypass the autoloader when performing that check.

If you wanted to ensure the controllers are loaded when needed, the CodeIgniter way, they should be added to the application as listed above.

Related