Artisan: как создать пользовательские команды make

Artisan: как создать пользовательские команды make

Довольно удобно создавать классы из консоли. Но что если в artisan не предусмотрены нужные команды? Никто не мешает нам самим сделать свою жизнь проще и комфортнее.

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

Прежде всего заглянем под капот и поинтересуемся, каким образом laravel генерирует классы/файлы каналов, событий, контроллеров и т.д. Для этого зайдём в директорию vendor/laravel/framework/src/Illuminate/Foundation/Console, в которой обнаружим целую пачку классов команд. Просмотрев файлы, название которых включает make мы поймём, что все они являются потомками класса GeneratorCommand, в котором есть практически всё что нужно - создание директорий и файлов, построение классов, замена неймспейсов и многое другое. Отлично - особо пачкать руки не придётся. Также обратим внимание на абстрактный метод getStub:

/**
 * Get the stub file for the generator.
 *
 * @return string
 */
abstract protected function getStub();

 

Этот метод мы обязаны реализовать и указать в нём путь к файлу, который будет использоваться генератором. В той же самой директории также найдём папочку с файлами-шаблонами. Например, так выглядит шаблон для модели model.stub:

<?php

namespace DummyNamespace;

use Illuminate\Database\Eloquent\Model;

class DummyClass extends Model
{
    //
}

 

Т.е. всё что нам нужно - по имеющимся образцам реализовать свои собственные классы, плюс под них написать шаблоны. Идём в консоль и выполняем:

php artisan make:command TraitMakeCommand

 

В результате будет создан файл app/Console/Commands/TraitMakeCommand.php. Открываем его и вставляем следующий код:

<?php

namespace App\Console\Commands;

use Illuminate\Console\GeneratorCommand;
use Symfony\Component\Console\Input\InputArgument;

class TraitMakeCommand extends GeneratorCommand
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $name = 'make:trait';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Create a new trait';

    /**
     * The type of class being generated.
     *
     * @var string
     */
    protected $type = 'Trait';

    /**
     * Replace the class name for the given stub.
     *
     * @param  string  $stub
     * @param  string  $name
     * @return string
     */
    protected function replaceClass($stub, $name)
    {
        $stub = parent::replaceClass($stub, $name);
        return str_replace('DummyTrait', $this->argument('name'), $stub);
    }

    /**
     * Get the stub file for the generator.
     *
     * @return string
     */
    protected function getStub()
    {
        return __DIR__.'/../stubs/trait.stub';
    }

    /**
     * Get the default namespace for the class.
     *
     * @param  string  $rootNamespace
     * @return string
     */
    protected function getDefaultNamespace($rootNamespace)
    {
        return $rootNamespace . '\Traits';
    }

    /**
     * Get the console command arguments.
     *
     * @return array
     */
    protected function getArguments()
    {
        return [
            ['name', InputArgument::REQUIRED, 'The name of the trait.'],
        ];
    }
}

 

Хотя код говорит сам за себя, всё же: в свойствах мы указали название и описание команды, а также тип "класса", который будет сгенерирован. Сказали, что будет один обязательный аргумент - название трейта (метод getArguments), объяснили где взять шаблон (getStub), дали указания касательно неймспейса (getDefaultNamespace, это, кстати, также означает, что если у нас нет директории app/Traits, она будет создана), и попросили заменить DummyTrait на то название, которое мы передадим как аргумент команды (replaceClass). Теперь в app/Console создадим каталог stubs, в котором поместим шаблон trait.stub. Вот его код:

<?php

namespace DummyNamespace;

trait DummyTrait
{
    //
}

 

Нужно ли что-то ещё? Нет, поскольку согласно официальной документации все команды, находящиеся в app/Console/Commands будут автоматически зарегистрированы в Artisan. Проверить, так ли это, можно выполнив:

php artisan list

 

и таки да! Среди всего прочего увидим:

make:trait           Create a new trait

 

Пробуем:

php artisan make:trait Billable

 

...и находим в app/Traits файл Billable.php с таким содержимым:

<?php

namespace App\Traits;

trait Billable
{
    //
}

 

Что и требовалось. Для контрактов (интерфейсов) процедура аналогична. Вот код файла app/Console/Commands/ContractMakeComand.php:

<?php

namespace App\Console\Commands;

use Illuminate\Console\GeneratorCommand;
use Symfony\Component\Console\Input\InputArgument;

class ContractMakeCommand extends GeneratorCommand
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $name = 'make:contract';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Create a new contract interface';

    /**
     * The type of class being generated.
     *
     * @var string
     */
    protected $type = 'Contract';

    /**
     * Replace the class name for the given stub.
     *
     * @param  string  $stub
     * @param  string  $name
     * @return string
     */
    protected function replaceClass($stub, $name)
    {
        $stub = parent::replaceClass($stub, $name);
        return str_replace('DummyContract', $this->argument('name'), $stub);
    }

    /**
     * Get the stub file for the generator.
     *
     * @return string
     */
    protected function getStub()
    {
        return __DIR__.'/../stubs/contract.stub';
    }

    /**
     * Get the default namespace for the class.
     *
     * @param  string  $rootNamespace
     * @return string
     */
    protected function getDefaultNamespace($rootNamespace)
    {
        return $rootNamespace . '\Contracts';
    }

    /**
     * Get the console command arguments.
     *
     * @return array
     */
    protected function getArguments()
    {
        return [
            ['name', InputArgument::REQUIRED, 'The name of the contract.'],
        ];
    }
}

 

и его шаблона:

<?php

namespace DummyNamespace;

interface DummyContract
{
    //
}

 

Пример команды:

php artisan make:contract Flyable

 

На этом на сегодня всё. Успехов!