How does [modus operandi] autoload in PHP work?

Asked

Viewed 566 times

3

Suppose I have the following file structure at the root of my site:

index.php
autoload.php

----| /Models
--------| /MainModel
------------| MainModel.php

----| /Controllers
--------| /MainController
------------| MainController.php

Suppose there is a method in Mainmodel.php called mainMethod() as follows:

<?php namespace Models\MainModel;

class MainModel
{

    public function mainMethod()
    {
        return json_encode(array('mensagem' => 'Tudo funcionando por aqui'));
    }
}

In the index php., i include the file autoload.php

<?php

include_once 'autoload.php';

// resto do código

My file autoload.php has the following code:

spl_autoload_register(function ($class) {

    $prefix = '';

    $base_dir = __DIR__.'/';

    $len = strlen($prefix);
    if (strncmp($prefix, $class, $len) !== 0) {
        return;
    }

    $relative_class = substr($class, $len);

    $file = $base_dir.str_replace('\\', '/', $relative_class).'.php';

    if (file_exists($file)) {
        require $file;
    }
});

Now comes the witchcraft that I don’t understand how it works. If I do:

<?php

include_once 'autoload.php';

$obj = new Models\MainModel\MainModel;


echo $obj->mainMethod();

// Output: {"mensagem": "Tudo funcionando por aqui"}

Even if I indicate the classes of controllers recognition is the same. It works!

Well, I have a closure autoload that works, but like this closure works? How the self-loading PHP recognizes the classes inside folders, even though I only indicated the project root?

PHP is going there inside the folders and recognizing the classes, as this is possible?

The great detail of it all is that if the file Mainmodel.php is not inside a folder Mainmodel (with the same file name) autoload doesn’t work.

Another curious thing is that the namespace have to indicate the file path to the class from the root Models\MainModel, then I declare the class.

I don’t want to know how to use autoload, because that I already got, but rather how it works, because I’m using something I don’t know how it works.

My question is completely different from the one chosen by the moderates of the site: see "possible duplicate"

1 answer

6


The spl_autoload_register intercepts all your calls that are made like this:

  • $foo = new AlgumaCoisa(); instantiate a class
  • echo AlgumaCoisa::teste; catch a constant
  • echo AlgumaCoisa::$teste; take a static variable
  • $baz = AlgumaCoisa::funcao(); calls a static method

It works as a "proxy", it runs before completing the class instance for example.

Now I recommend not using require or require_once, and neither file_exists, do so:

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

This is because it can affect errors because if the expected errors is Class not found when the class does not exist your code will issue a require error, which is right, but not for the PSR.

And the file_exists checks files and folders if you happen to have a folder named qualquercoisa.php can break the application, is_file has no significant performance difference if compared file_exists, maybe only if you run 100,000 classes at the same time you lose 0,0002 seconds.

I recommend using one of these examples:

If it is PSR-4 should look like this, (I adapted for your case and simplified well by removing what I believe would not be used):

<?php
spl_autoload_register(function($class) {
    $class = ltrim($class, '\\');

    $file = str_replace('\\', '/', $class) . '.php';

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

factor use an anonymous function or is not indifferent.

Understanding the code

Like I said, you write the spl_autoload as desired, there are several ways to implement it, but I will explain the code I wrote:

  • Remove \ from the beginning (it is a bug in php 5.3, in the other versions this problem does not exist, so basically it is a "backward compatibility"):

    $class = ltrim($class, '\\');
    
  • For example, if $class receives Models\MainModel\MainModel, this will exchange \ for /:

    $file = str_replace('\\', '/', $class) . '.php';
    

    And generates it in the variable $file:

    "Models/MainModel/MainModel.php"
    

Then from the value of $file let’s check if the file exists and if it exists it will include:

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

And then the process of calling the new Models\MainModel\MainModel will continue

  • P: The great detail of all this is that if the Mainmodel.php file is not inside a Mainmodel folder (with the same file name) autoload does not work.

  • R: autoload works, it just can’t do include, because it couldn’t find the file

  • P: Another curious thing is that the namespace have to indicate the file path to the class from the Mainmodel root, then I declare the class.

  • R: This is relative, you implement as you wish, but in almost all languages namespace is represented by a structure of folders and files, it has languages that do not, like C++, where the file can be anywhere, because his include is not done when using the class but when using #include "localfisico.h"

  • I already gave +1 to the two answers and used them as a reference. I changed the details of the functions is_file() and files_exists() and I’m using include. But you could explain more how autoload "pulls" the file classes and why a specific file structure is needed (or not, because in my case it only worked with a specific structure)?

  • 1

    @Nottherealhemingway edited the answer and detailed all points of implementation, I hope it helps

  • 4

    Great question and great answer. Congratulations to both of you. : D

  • 1

    Perfect! Thank you, @Guilhermenascimento. You saved my arse Twice Today

  • 1

    @Nottherealhemingway added two explanations to his specific doubts at the end of the answer.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.