Сегодня закончил писать класс автозагрузки классов. Задача для меня была сложная, но результат понравился.

Так и хочется процитировать великого классика — «Ай да Пушкин! Ай да сукин сын!». Безусловно я адекватно понимаю, что моя сегодняшняя работа может мне нравится только на том уровне обучения на котором я нахожусь сейчас. Более того я знаю что есть composer который решает проблему автозагрузки намного проще и вообще похоже является стандартом.

Так почему же я доволен как слон после клизмы изобретя очередной велосипед?

Да очень просто я сделал что-то сам. Решил задачу которая мне раньше казалось архисложной. А тут вот сел, подумал и решил! Эмоции переполняют, хочется продолжать учиться дальше, писать новый код, решать следующие задачи.

Теперь о самом классе автозагрузки.
Перед собой я поставил задачу написать класс который будет автоматически загружать классы без постоянных require в каждом файле. При этом все системные классы должны загружаться из так называемой карты классов. Карта классов это массив в котором содержатся имена всех классов и пути к файлам.
Так же Autoloader должен загружать классы через namespace из разрешённых папок app и vendor, а вслучае если приложение работает в режиме разработчика, тогда классы должны загружаться ещё и из папки test.
И ещё один момент нужно учесть. Для себя я принял соглашение заканчивать имена всех классов .class.php. Однако если я захочу использовать стороннюю библиотеку. Но где гарантия что разработчик библиотеку которого я решу установить будет делать также? Конечно нигде и высока вероятность что в сторонней библиотеке файлы будут иметь расширение .php без явного указания, что это именно класс.

Вот что у меня получилось:

<?php

class Autoloader
{
    private static $istance;

    private function __construct()
    {
        spl_autoload_register( [ $this , 'load' ] , true , false );
    }

    public static function getInstance()
    {
        if (!isset(self::$istance)) {
            self::$istance = new self;
        }
        return self::$istance;
    }

    private function load ( $class )
    {
        require_once $this->getClass( $class );
    }

    private function getClass($class)
    {
        $namespace = explode( '\\' , $class );
        $path = isset($namespace[1]) ? $this->otherClasses($class) : $this->systemClasses($class);

        try {
            if ( !is_readable( $path )) {
                throw new Exception( 'Нет файла с классом: ' . $class );
            }
            return $path;
        } catch (Exception $e) {
            echo $e->getMessage();
            die();
        }
    }

    private function systemClasses ( $class )
    {
        $map =  './core/config/MapClasses.php';
        require $map;

        try {
            if ( !isset( $mapClasses[$class] )) {
                throw new \Exception( ' Класс ' . $class . ' не зарегистрирован' );
            }
            return $mapClasses[$class];
        } catch (Exception $e) {
            echo $e->getMessage();
            die();
        }
    }

    private function allowedFoldersLoadingClasses()
    {
        $folders = [
            'app',
            'vendor'
        ];
        if ( 'DEV' === MODE ) {
            $folders[] = 'test';
        }

        return $folders;
    }

    private function otherClasses ( $class )
    {
        foreach ($this->allowedFoldersLoadingClasses() as $dir) {
            $file = './' . $dir . '/' . str_replace( '\\' , '/' , $class ) . '.class.php';
            if (!is_file($file)) {
                $file = './' . $dir . '/' . str_replace( '\\' , '/' , $class ) . '.php';
            }

            if (is_file( $file )) {
                $path = $file;
                return $path;
            }
        }
        return false;
    }

    private function __clone () {}
}

Попутно прилось немного поправить файл с картой классов теперь он выглядит так:

<?php

$mapClasses = [
    'Route' => './core/classes/utils/Route.class.php',
    'UrlParse' => './core/classes/utils/UrlParse.class.php'
];

Тесты я пока писать не научился, однако в целях тестирования я всё же создал несколько файлов с классами для тестов.

В папке app/blog я создал файл Controller.class.php с кодом

<?php
namespace blog;

class Controller
{
    public function __construct()
    {
        
    }

    public static function blog()
    {
        echo __METHOD__;
    }
}

Здесь один статический метод который просто выводин на экран название метода blog.

Так же в файле Route.class.php

<?php

class Route
{
    public static function bootstrap()
    {
        echo __METHOD__;
    }

}

И в файле UrlParse.class.php

<?php

class UrlParse
{
    public function __construct()
    {
        
    }
    
    public static function urlParse()
    {
        echo __METHOD__;
    }
}

И в папке test/testAutoloader создал файл TestAutoloader.php

<?php

namespace testAutoloader;

class TestAutoloader
{
    public function test($a)
    {
        $a = $a+$a;
        echo $a;

    }

}

Таким образом Controller.class.php и TestAutoloader.php должны загрузиться с помощью namespace. При этом класс TestAutoloader имеет расширение .php в отличие от класса Controller у которого расширение .class.php. Классы Route и UrlParse прописанны в карте классов.

Открываю файл index.php и вношу в него несколько строк для вызова вновь созданных классов:

<?php

$systemCofig = __DIR__ . '/core/config/SystemConfig.php';

if (!is_readable($systemCofig)) {
    header('location:install.php');
    die('Нет файла конфигурации');
}

require_once $systemCofig;
Route::bootstrap();
echo '<hr>';
UrlParse::urlParse();
echo '<hr>';
\blog\Controller::blog();
echo '<hr>';
$test = new \testAutoloader\TestAutoloader;
$test->test(10);

Иду в браузер и вижу результат

Олично все 4 класса загрузились, всё работает как я и планировал!

Продолжение следует…