PSR-4 in an MVC project or not?

9

I have two codes, the first one uses spl_autoload_register and the other does not, however the second one loads "automatically the class" as well.

With spl_autoload_register

  • Uses namespaces to split MVC
  • You can create multiple levels of folders following the idea of PSR-4
  • Controllers and Models can have the same name as each one is within a different namespace.

Code:

spl_autoload_register(function ($class)
{
    $np = explode('\', $class);

    $base = strtolower($np[0]);

    switch ($base) {
        case 'controller':
        case 'model':
            $base = 'application/' . $base . 's';
        break;
        default:
            return NULL;
    }

    array_shift($np);

    $relative_class = strtolower(implode('/', $np));

    $file = './' . $base . '/' . $relative_class . '.php';
    /*
     * resulta em algo como:
     * ./application/controllers/foo/test/user.php
     * ./application/models/foo/abc/user.php
     */

    if (is_file($file)) {
        require_once $file;
    }
});

Calling an action from a controller:

$controller = new \Controller\foo\test\user;
$controller->profile();

Calling a model:

$model = new \Model\foo\test\user;

With methods

  • Do not use namespaces
  • "Eventually" may be easier to understand / use than the previous one
  • Controllers and Models can not have the same name, but it's no problem since we can use prefixes
  • Supports subfolders.

Code:

<?php
class App
{
    static private function prepare($path)
    {
        $fp = explode('.', $path);

        return array(
            'name' => end($fp),
            'path' => implode('/', $fp)
        );
    }

    static public function model($name)
    {
        $data = self::prepare($name);

        if (is_file($data['path'])) {
            require_once './application/models/' . $data['path'] . '.php';
        }

        return new $data['name'];
    }

    static public function action($name, $action)
    {
        $data = self::prepare($name);

        if (is_file($data['path'])) {
            require_once './application/controllers/' . $data['path'] . '.php';
        }

        $controller = new $data['name'];
        $controller->$action;
    }
}

Calling an action from a controller:

App::action('foo.test.user', 'profile');

Calling a model:

$model = App::model('foo.abc.user');

My question is, should I use the simplest way without namespaces and spl_autoload or not? How can I work on or improve these codes to make it easier for the final developer to use?

    
asked by anonymous 10.08.2015 / 22:18

1 answer

8

My recommendation would be to directly use an autoloader that implements the PSR-4, like the one included in Composer. Besides being a standard already fully adopted in the community, the configuration is simpler and you do not have to reinvent the wheel.

Considering that your examples are for didactic purposes, I have some considerations:

Using namespaces and spl_autoload_register make it possible to create a more extensible structure for the application. In small projects, a collision with the class name rarely occurs, but as you begin to introduce external libraries, the chance of this happening increases.

The concept of namespaces is common in other languages, such as packages in Java, namespaces in C #, which makes the concept already known to the software developers.

spl_autoload_register does require of classes or interfaces only when needed, including when doing extends or implements .

Currently your autoload function only supports classes within model and controller , because the default case gives a return in autoload without returning any file.

I would remove the switch that adds s to the controllers and models and would allow the autoload to accurately reflect the class namespace:

spl_autoload_register(function ($class)
{
    $np = explode('\', $class);
    $relative_class = strtolower(implode(DIRECTORY_SEPARATOR, $np));

    $file = './' . $relative_class . '.php';

    if (is_file($file)) {
        require_once $file;
    }
});

Although the second form seems to meet your needs, it is more limited than using spl_autoload_register . It may work well with Models or Controllers , but I see the following issues:

  • Refactoring the application can become a difficult task, as the reference to the class is a% colon delimited by points. When using the full reference of the class we can use an IDE to rename this class and change its references. With string I do not see a safe way to do this other than using regular expressions throughout the project or a replace all . There is even a constant included in PHP 5.5 calling string , which returns the complete class path with namespace such as string .

  • The ::class class is not exactly an autoloader . Only two actions are possible: return an instance of a particular model or perform the controller action. PHP will not automatically load the classes needed for your application, limited the use of object-oriented features such as inheritance and interfaces, unless the parent class or interface has already been previously included with a App which makes the code even more complex than using require and namespaces . See this example considering the classes below

    // classe bar em ./application/controllers/bar.php
    class bar{
    
        public function run(){ echo "Hello World"; }
    
    }
    
    // classe baz em ./application/controllers/baz.php
    //
    // só é garantido que essa classe irá funciona se descomentar a linha abaixo
    // require_once 'application/controllers/bar.php';      
    class baz extends bar {}
    
    // arquivo principal
    
    // Executar esse método nesse momento resultará em um erro, pois a classe bar ainda foi carregada na aplicação
    //App::action('baz', 'run');
    
    // Isso vai funcionar como esperado, irá escrever na tela
    App::action('bar', 'run');
    
    // Agora baz vai funcionar pois bar já foi carregado na linha acima
    App::action('baz', 'run');
    
11.08.2015 / 01:39