Создание расширения (пакета) для Laravel 5.

Создание автономного пакета — вещь очень удобная, т.к. позволяет легко подключать и использовать для других своих (и не только) проектов. В данной статье рассмотрим процесс создания такого пакета на примере одного из моих пакетов для работы с виджетами в Laravel.
Страница данного пакета на GitHub https://github.com/klisl/laravel-widgets
Недавно я опубликовал статью Виджеты в Laravel 5 в которой довольно подробно описал функционал пакета который мы с вами будем создавать. Поэтому не буду останавливаться на тонкостях. Наша цель — это, в первую очередь, оформление кода в виде пакета.
Для разработки своего пакета вы должны уметь работать с git (распределённая система управления версиями), а так же иметь аккаунт на GitHub (Bitbucket).
Оформление пакетов имеет свои правила. Корневую папку пакета нужно назвать одноименно своему логину на GitHub. Внутри создаем папку с названием проекта. Удаленный репозиторий для данного пакета нужно будет назвать так же.
В моем случае это:
klisl\laravel-widgets
Способы размещения каталога будущего пакета для его разработки есть разные. Например можно создать каталог packages в корне одного из ваших laravel-проектов. Тогда получится такая структура:
laravel.loc\packages\klisl\laravel-widgets
Есть способ, при котором в начале создается удаленный репозиторий будущего пакета, регистрируется на https://packagist.org, устанавливается как пакет в проект Laravel (в папку vendor) и только тогда начинают работать над его функционалом. В этом случае, структура такая:
laravel.loc\vendor\klisl\test-package
Мы же рассмотрим третий, и по-моему, самый удобный вариант, когда пакет оформляется как отдельный проект.
Для этого создаем каталог klisl наряду с другими вашими проектами, тем же laravel, например. Внутри создаем папку с названием пакета «laravel-widgets».
У меня на OpenSer так W:\domains\klisl\laravel-widgets
В корне пакета нужно создать файл composer.json. Для этого существует команда

composer init

которую нужно выполнить из консоли находясь в laravel-widgets. Нужно будет ответить на некоторые вопросы, после чего файл автоматически сгенерируется.
На данные вопросы можно ввести «n», т.к. с зависимостями вашего пакета еще не понятно:
Would you like to define your dependencies (require) interactively [yes]?
Would you like to define your dev dependencies (require-dev) interactively [yes]?

Так же можно просто скопировать файл composer.json из другого проекта и откорректировать. Получиться должно примерно следующее:

{
    "name": "klisl/laravel-widgets",
    "description": "Package for using widgets in Laravel-5",
     "type": "library",
     "keywords": [
        "laravel",
        "widgets"
    ],
    "license": "MIT",
    "authors": [
        {
            "name": "YourName",
            "email": "yourmail",
            "homepage": "https://github.com/klisl/laravel-widgets",
            "role": "Developer"
        }
    ],
     "minimum-stability": "stable",
     "require": {
        "php": ">=5.4.0"
    },
    "autoload": {
        "psr-4": {
            "Klisl\\Widgets\\": "src"
        }
    }
}

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

Основной функционал пакета, php файлы, принято размещать в каталоге src. Как я писал выше, сейчас подробно не останавливаюсь на описании функционала пакета. Больше можно почитать в статье Виджеты в Laravel 5 и в комментариях к коду.
В src я разместил:

Основной файл виджета Widget.php:

<?php
namespace Klisl\Widgets;
class Widget{
 protected $widgets; //массив доступных виджетов config/widgets.php
 public function __construct(){
    $this->widgets = config('widgets');
 }
 public function show($obj, $data =[]){
    //Есть ли такой виджет
    if(isset($this->widgets[$obj])){
        //создаем его объект передавая параметры в конструктор
        $obj = new $this->widgets[$obj]($data);
        //возвращаем результат выполнения
        return $obj->execute();
    }
 }
}

Сервис-провайдер, файл WidgetServiceProvider.php:

<?php
namespace Klisl\Widgets;
use Illuminate\Support\ServiceProvider;
use App;
use Blade;
class WidgetServiceProvider extends ServiceProvider
{
    public function boot()
    {
        //Указываем, что файлы из папки config должны быть опубликованы при установке
        $this->publishes([__DIR__ . '/../config/' => config_path() . '/']);
        //Так же публикуем тестовый виджет с каталогом для пользовательских виджетов
        $this->publishes([__DIR__ . '/../app/' => app_path() . '/']);
        /*
         * Регистрируется директива для шаблонизатора Blade
         * Пример обращения к виджету: @widget('menu')
         * Можно передать параметры в виджет:
         * @widget('menu', [$data1,$data2...])
        */
        Blade::directive('widget', function ($name) {
        return "<?php echo app('widget')->show($name); ?>";
        });
        /*
         * Регистрируется (добавляем) каталог для хранения шаблонов виджетов
         * app\Widgets\view
         */
        $this->loadViewsFrom(app_path() .'/Widgets/views', 'Widgets');
    }
    public function register()
    {
        App::singleton('widget', function(){
            return new \Klisl\Widgets\Widget();
        });
     }
}

Совсем кратко о том, что делает данный класс. При начальной загрузке приложения Laravel, автоматически выполнится метод register(), который создаст объект главного класса виджета Klisl\Widgets\Widget и сохранит его в сервис-контейнере приложения. Уже позже, в методе boot() данный объект будет использован для создания директивы @widget шаблонизатора, для переадресации на него при вызове директивы.
В методе boot() так же прописано, что нужно будет (при выполнении специальной команды в консоли) скопировать указанные папки и файлы в корень приложения (в моем случае), то есть они должны быть опубликованы.
В папке Contract разместил контракт (интерфейс) общий для всех пользовательских виджетов
Файл ContractWidget.php:

<?php
namespace Klisl\Widgets\Contract;
interface ContractWidget {
     /**
     * Основной метод любого виджета, который должен возвращать вывод шаблона:
      *  return view('Widgets::NameWidget', [
      *'data' => $data
      * ]);
      */
     public function execute();
}

В каталоге app я разместил папку с названием «Widgets», в которой должны храниться все классы виджетов и папка с их шаблонами:

Я решил разместить там класс тестового виджета, для того, что бы можно было протестировать работу пакета, а кроме того удобно создавать свои виджеты на его основе.
Файл TestWidget.php:

<?php
namespace App\Widgets;
use Klisl\Widgets\Contract\ContractWidget;
class TestWidget implements ContractWidget{
 public function execute(){
    return view('Widgets::test');
 }
}

Шаблон для тестового виджета, файл test.blade.php:

<div>
 Тест виджета.
</div>

Осталась папка config. Одноименная папка есть в корне приложения Laravel. Я решил хранить кое-какую служебную информацию в файле конфигурации, поэтому создал там файл, который назвал widgets.php:

<?php
/*
 * Ключ: краткое название виджета для обращения к нему из шаблона
 * Значение: пространство имен виджета (путь)
 */
return [
 'test' => 'App\Widgets\TestWidget',
];

Как я указал в сервис-провайдере (файл WidgetServiceProvider.php), каталог app вместе с тестовым виджетом и данный файл конфигурации будут скопированы в корень приложения Laravel при установке пакета. Это удобно — пользователю не нужно лезть в папку vender в файлы пакета и структура папок для размещения своих виджетов уже будет подготовлена. В пакетах, так же, часто публикуют (копируют в скелет приложения) файлы миграций, шаблоны…
На этом функционал пакета готов.
Папка .git, как вы должны были догадаться, создастся при инициализации git в пакете перед отправкой его на удаленный репозиторий. Файл README.md позволяет создать красивое описание вашему пакету. Как оформить его можно прочитать, например, тут. Файл LICENSE можно создать прямо на GitHub (впрочем как и README.md) или скопировать из моего пакета. При создании composer.json я указал тип лицензии MIT, это значит, что все могут пользоваться пакетом без ограничений на свой страх и риск.
Если вы создаете пакет и хотите, чтобы другие программисты могли его найти и пользоваться, то нужно дополнить файл composer.json. В блок keywords включить ключевые слова по которым его можно будет найти в поисковике репозиториев, указать свои данные в поле authors, в поле require указать зависимости вашего пакета и тд.
Касательно объекта autoload:

    "autoload": {
        "psr-4": {
            "Klisl\\Widgets\\": "src"
        }
    }

Тут я указал, что при размещении классов пакета и при указании их пространств имен был использован стандарт psr-4, который и рекомендуется на данный момент. Если очень кратко про этот стандарт, то, например, класс TestWidget у меня находится в пространстве имен App\Widgets и одновременно в папке app\Widgets, что повторяет это пространство имен и помогает автозагрузчику классов легко найти нужный файл. Кроме того, название файла должно повторять название созданного в нем класса (название можно немного модифицировать в коде).
Строкой «Klisl\\Widgets\\»: «src» указано исключение из этого правила. А именно — искать пространство имен Klisl\Widgets в папке src. То есть, встретив данное пространство имен, автозагрузчик классов будет искать файлы не в папке Klisl\Widgets, а в каталоге src. Это необходимо для создания структуры пакета такой как нам удобно.

Подключение пакета к проекту Laravel для проверки.

На данном этапе пакет расположен у нас в виде отдельного проекта на уровень выше файла composer.json вашего Laravel-приложения. Для проверки работоспособности, необходимо подключить его. Для этого добавляем в файл composer.json Laravel-приложения, в котором будем тестировать блок:

"repositories": [
        {
            "type": "path",
            "url": "../klisl/laravel-widgets"
        }
]

где в поле url нужно указать путь до файла composer.json пакета.
После этого нужно подключить пакет для формирования автозагрузки классов с помощью Composer. Выполнить в консоли:

composer require klisl/laravel-widgets:dev-master --prefer-source

Теперь Laravel-приложение имеет доступ к классам пакета.
Для того, чтобы созданный сервис-провайдер (файл WidgetServiceProvider.php) выполнил свою работу, его нужно подключить. Делается это в файле config\app.php, где нужно добавить строку в массив «providers»:

Klisl\Widgets\WidgetServiceProvider::class,

Теперь наш сервис-провайдер стартует вместе с другими провайдерами при загрузке приложения.
Так же, в нем мы указывали ресурсы которые должны быть скопированы в указанные папки. Для этого выполнить в консоли команду:

php artisan vendor:publish --provider="Klisl\Widgets\WidgetServiceProvider"

После этого в каталоге app создастся папка Widgets с файлами, и добавится файл widgets.php в папку config.
Все готово. Запустим тестовый виджет. Для этого добавим в любой шаблон который собираетесь отобразить, например в resources\views\welcome.blade.php строку:

@widget('test')

Если после этого увидите на экране строку
Тест виджета.
значит все было сделано правильно.

Отправка пакета на GitHub и публикация на packagist.org

Отправляем пакет в удаленный репозиторий на GitHub. Примерный набор команд для этого:

git init
git add .
git commit -m "First commit"
git remote add origin https://github.com/klisl/laravel-widgets.git
git push -u origin master
git tag -a 1.0 -m "New version"
git push --tags

где после строки git remote add origin нужно подставить адрес вашего репозитория. Как видите, он повторяет название корневой папки (поставщика) и папки с пакетом. Так же, тут указываем версию пакета, что понадобится, если вы собираетесь опубликовать его в репозитории пакетов на сайте https://packagist.org
Сервис packagist.org используется Composer для поиска пакетов. Поэтому вы можете так же зарегистрироваться там и указать URL к вашему пакету на GitHub по нажатию кнопки Submit. Это если вкратце.

Проверяем удаленную установку своего пакета с помощью Composer.

Установка достаточно проста.

  • Загрузка пакета. В консоли:
composer require klisl/laravel-widgets
  • Добавить в config\app.php в массив «providers» строку:
Klisl\Widgets\WidgetServiceProvider::class,
  • Публикация файлов. В консоли:
php artisan vendor:publish --provider="Klisl\Widgets\WidgetServiceProvider"

Опять же, для проверки, добавляем в любой шаблон который собираетесь отобразить, например в resources\views\welcome.blade.php строку:

@widget('test')

Если и на этот раз вы видите сообщение виджета на экране, значит его смогут увидеть и другие!
А если что-то не будет получаться, всегда можно посмотреть исходники моего пакета на GitHub laravel-widgets