Composer как стандарт для автозагрузки php классов

Как-то на днях хотел посидеть с какой-то интересной темой из области “PHP без фреймворков”, просто так. Но также при этом хотелось чтобы была автозагрузка файлов и неймспейсы.
И как-то сразу же вспомнил как для одного из тестовых заданий нужно было быстро собрать очень простое mvc приложение и для автозагрузки плюс еще некоторых зависимостей я использовал composer. Дальше как раз про это и написано.

Что хочется чтобы было) :

  • Необходимо чтобы была автозагрузка файлов.
  • Необходимо, чтобы поддерживались неймспейсы.
  • Имелась возможность поставить зависимости, например тот же phpunit.

Для начала установка composer. У меня уже стоит, но лишний раз можно повторить.
На сайте getomposer.org описан процесс у себя я тоже делал короткий how-to как установить композер.

Основное отличие — composer на виртуалке установлен глобально и команда вместо php composer.phar … выглядит короче: composer …

В папке будущего проекта создаю новый файл composer.json и в нем пусть будет только {}
В этом случае у нас нет никаких зависимостей и настроек, поэтому минимально необходим только валидный composer.json — т. е. содержащий как минимум просто фигурные скобки.

Выполняем

$ composer install

Композер создает папку vendor внутри нее папку composer со всем нужным для загрузчика классов ClassLoader и еще внутри vendor файл autoload.php, который возвращает загрузчик.

В принципе, если интересно, то можно покопаться, например, там есть /vendor/composer/autoload_classmap.php, который возвращает карту классов. Но этот файл работает для классов, которые устанавливает сам композер.

Меня в данный момент больше интересует следующий вопрос:
Автоматическая загрузка моих классов, никак не относящихся к зависимостям композера и чтобы была поддержка неймспейсов.

Для примера мини-проект

Структура проекта

Для конкретики пусть будет такой мини-проект.
Есть приложение, которое должно прочитать json вида

    [
        { "symbol" : "EUR", "rate" : "2"},
        {"symbol" : "USD", "rate" : "4"}
    ]

Этот json будет получаться по ссылке и нужно записать его в таблицу бд.
Приложение пусть будет отдельным классом, за работу с таблицей пусть отвечает один класс, а за соединение другой.

Итак. Структура файлов проекта

/ ← project directory
  /src ← тут будут все мои классы и т. п.
    Application.php ← тут класс приложения
    /database ← тут все классы для работы с базой данных
      Connection.php ← класс, который отвечает за соединение с бд
    /models ← тут модели, в данном случае для работы с таблицами
      Currency.php ← класс, для работы с таблицей
  /vendor ← тут все что обслуживается командами композера
    /composer
    autoload.php ← тут подключается сгенерированный композером загрузчик
  index.php ← для простоты тут будет входная точка для моего мини-проекта

Конечно, index.php может быть уже занят, тогда можно будет сделать другой файл, например config.php или bootstrap.php.
Собственно создание бд и таблицы описывать не буду (как создавать бд можно посмотреть тут).

Класс приложения

namespace SHR;


use SHR\database\Connection;
use SHR\models\Currency;

class Application {

    /** @var  Connection */
    private $_connection;

    /** @var  Currency */
    private $_model;
    
    private $host = 'localhost';
    private $database = 'currencydb';
    private $user = 'username';
    private $password = 'password';

    public function __construct(){
        try {
            $this->_connection = new Connection();
            $this->_connection->connect( $this->database, $this->user, $this->password, $this->host );
        } catch ( Exception $e ) {
            throw new \RuntimeException($e->getMessage());
        }
    }


    public function updateCurrencyData(){
        $this->_model = new Currency( $this->_connection->getConnection() );
        $data = $this->readData();
        $this->currencyUpdate($data);
    }

    private function currencyUpdate($currencies){
        foreach($currencies as $symbol=>$rate){
            $this->_model->symbol = $symbol;
            $this->_model->rate = $rate;
            $this->_model->save();
        }
    }

    private function readData(){
            $curlOpts = [
                CURLOPT_URL => 'http://currency.local/rates1.json',
                CURLOPT_RETURNTRANSFER => 1
            ];
            $curl = curl_init();
            curl_setopt_array($curl,$curlOpts);
            $response = curl_exec($curl);
            $code = curl_getinfo($curl,CURLINFO_HTTP_CODE);
            
            curl_close($curl);
            if($code === 200){
                $response = json_decode($response,true);
                return $response;
            }else{
                throw new RuntimeException('curl error: code = '.$code);
            }
    }

}

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

В конструкторе создаем соединение по заданным в полях класса параметрам.
Потом откуда-то извне вызываем updateCurrencyData, который создает новый экземпляр модели.
Далее вызывается чтение данных readData, который через curl получает json с данными.
Для простоты будем считать, что приходит валидный json с нужной структурой.
В конце вызывается currencyUpdate, в котором заполняются поля модели и модель сохраняется.

Класс Connection

Он совсем простой:

namespace SHR\database;


class Connection {

    /** @var  \PDO */
    private $pdo = null;
    

    public function connect( $database, $username, $password, $host = 'localhost' ) {
        $this->pdo = new \PDO( "mysql:dbname=$database;host=$host",$username,$password);
    }

    public function getConnection(){
        return $this->pdo; 
    }
}

Создается новый экземпляр PDO по переданным параметрам.
Через getConnection возвращается тому кто спросил.
Никаких обработок ошибок, return $this для цепочек — все очень просто.

Класс модели Currency

Это тоже простой вариант ActiveRecord. Этот класс объясню в другой раз.

Что интересует — так это начало класса

namespace SHR\models;

/**
 * Class Currency
 * @package SHR\models
 *
 * @property string $symbol
 * @property integer $rate
 *
 */
class Currency {

Все теперь есть понимание какие классы и неймспейсы должны работать. Вот они:

namespace SHR; class Application {}

namespace SHR\database; class Connection {}

namespace SHR\models; class Currency {}

Теперь нужна точка входа. В качестве не я выбрал файл index.php.
В нем нужно сделать обязательно следующее — получить объект загрузчика и указать ему какие я хочу использовать неймспейсы и где их искать.

use SHR\Application;

$loader = require( __DIR__ . '/vendor/autoload.php' );
$loader->addPsr4( 'SHR\\', __DIR__ . '/src/' );

$appication = new Application();
$appication->updateCurrencyData();

В начале прописался раздел use, т. к. тут я создаю экземпляр Application
В последних двух строчках создается Application и вызывается метод обновления данных.

А в оставшихся строчках магия от композера.

В $loader я получаю экземпляр загрузчика из сгенерированным композером autoload.php
И в методе addPsr4 добавляю имя своего верхнего неймспейса SHR и говорю, что искать его надо в каталоге src.

Вообще мне больше по душе PSR-0 — в нем неймспейс точно ложится на файловую структуру. Но тут я использую добавление неймспейса по PSR-4, и следовательно могу не создавать каталог SHR, ну и на будущее задел.

Вот и все. Да, можно еще указать загрузку через composer.json, но мне для экспериментов удобней через вызов метода.

Загрузка только через композер

Загрузку можно сделать так:

В composer.json добавляем раздел autoload, а в него psr-0 или psr-4 и указываем свои неймспейсы, для моего примера будет выглядеть так:

{ 
   "autoload": {
        "psr-4": {
          "SHR\\":"/src/"
        }
    }
}

теперь выполняем команду

composer dump-autoload -o

Выдастся сообщение

Generating optimized autoload files

Все – композер перегенерил класс автозагрузчика и добавил туда мой неймспейс.
После этого нужно убрать $loader->addPsr4 .

В общем с началом использования композера множеству реализаций автозагрузки я предпочитаю именно через него.

3 Comments
  1. Виктор
    • Виктор
      • shrewmus

Leave a Reply

Your email address will not be published. Required fields are marked *