В этой статье вы узнаете, как использовать инструмент командной строки Artisan от Laravel и как создать настраиваемую команду. Обратите внимание, что вы должны быть знакомы с фреймворком Laravel, чтобы получить большую часть этой статьи.
Что мы строим
В этом уроке мы собираемся создать команду для минимизации наших ресурсов CSS, которая будет использоваться следующим образом:
cssmin 'output_path' 'file1' ... 'fileN' -- comments -- concat
-
output_path
: (обязательный) путь для сохранения минимизированных файлов (style.css
->style.min.css
). -
file1 ... fileN
: (обязательный) список файлов для минимизации. -
--comments
: (необязательно) добавьте эту опцию, чтобы оставлять комментарии. -
--concat
: (необязательно) объединить минимизированные файлы в один файл с именемall.min.css
.
Что такое команда Laravel
Artisan
— это название утилиты командной строки в Laravel. Он поставляется с набором предопределенных команд, которые вы можете перечислить с помощью списка php artisan list
. Если вы хотите показать справку для определенной команды, вы можете использовать php artisan help command
.
Создание команды Css Minifier
Чтобы создать команду ремесленника, вы можете использовать command:make
command. Эта команда принимает один аргумент:
-
name
:name
класса для команды.
и три варианта:
-
--command
: имя, которое нужно ввести для запуска команды. -
--path
: по умолчанию команды хранятся в папкеapp/commands
, однако вы можете изменить это с помощью этой опции. -
--namespace
: вы можете использовать эту опцию для пространства имен вашего набора команд, например, в командеcommand:make
, командаmake
находится в пространстве именcommand
.
Теперь, чтобы создать нашу команду, мы будем использовать php artisan command:make CssMinCommand --command=cssmin
которая создаст файл CssMinCommand.php
в нашем каталоге app/commands
.
use Illuminate\Console\Command; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; class CssminCommand extends Command{ protected $name = 'cssmin'; protected $description = 'Command description.'; public function __construct(){ parent::__construct(); } public function fire(){ // } protected function getArguments(){ return array( array('example', InputArgument::REQUIRED, 'An example argument.'), ); } protected function getOptions(){ return array( array('example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null), ); } }
Наш класс CssMinCommand
расширяет Illuminate\Console\Command
и переопределяет два метода ( getArguments
, getOptions
).
-
getArguments
: эта функция возвращает массив аргументов, которые должны быть переданы команде (например: список файлов, которые мы передаем командеcssmin
). -
getOptions
: возвращает список параметров или переключателей, которые вы можете передать команде. (например,--comments
).
Примечание: параметры могут иметь или не иметь значения, --comments
— это только флаг, который возвращает true
если он передан команде, тогда как --ouptput='public/assets'
вернет значение.
Когда ваша команда выполняется, вызывается метод fire
, так что именно здесь мы должны поместить нашу командную логику.
Регистрация команды
если вы попытаетесь запустить нашу команду php artisan cssmin 'args'
вы получите Command "cssmin" is not defined
.
Чтобы зарегистрировать команду, вам нужно добавить ее в файл artisan.php
:
Artisan::add( new CssMinCommand ); //or through the container Artisan::add( App::make("CssMinCommand") );
Если вы не хотите помещать свои команды в файл artisan.php
, вы можете создать отдельный файл и включить его, или, если вы создаете пакет, вы можете зарегистрировать их в своем поставщике услуг .
аргументы
В нашем методе getArguments
мы определим наш output
и files
.
Чтобы определить аргумент, нам нужно передать массив значений:
array ( 'name' , 'mode' , 'description' , 'defaultValue' )
-
name
:name
ключа, которое будет использоваться при получении аргументов. -
mode
: может иметь один из трех вариантов:-
InputArgument::REQUIRED
: аргумент обязателен. -
InputArgument::OPTIONAL
: аргумент является необязательным. -
InputArgument::IS_ARRAY
: аргумент принимает несколько значений (например,file1...fileN
).
Однако вы можете комбинировать их как
InputArgument::IS_ARRAY | InputArgument::REQUIRED
InputArgument::IS_ARRAY | InputArgument::REQUIRED
(аргумент обязателен и должен быть массивом). -
-
description
: полезно при выводе команды help. -
defaultValue
: если аргумент не был предоставлен.
Итак, наш метод getArguments
будет:
protected function getArguments(){ return array( array( 'output', InputArgument::REQUIRED, 'Path to output directory' ), array( 'files', InputArgument::IS_ARRAY | InputArgument::OPTIONAL , "List of css files to minify" ), ); }
Примечание: при использовании аргумента IS_ARRAY
он должен быть последним в массиве возвращаемых аргументов. (Очевидно).
Параметры
Наша команда cssmin
будет иметь только две опции. Чтобы определить опцию, мы передаем массив:
array ( 'name' , 'shortcut' , 'mode' , 'description' , 'defaultValue' )
-
name
: название вашего варианта (например,comments
). -
shortcut
: более короткая версия вашего варианта (например:--verbose
и-v
). -
mode
: может быть одним из четырех параметров (InputOption::VALUE_IS_ARRAY
,InputOption::VALUE_OPTIONAL
,InputOption::VALUE_REQUIRED
,InputOption::VALUE_NONE
), первые три значения аналогичны аргументам.-
VALUE_NONE
: указывает, что опция является логическим флагом (например:--verbose
).
-
-
description
: полезно при выводе команды help. -
defaultValue
: если значение параметра не было предоставлено.
Итак, наш метод getOptions
будет:
protected function getOptions(){ return array( array('comments', 'c', InputOption::VALUE_NONE, 'Don\'t strip comments' , null), array('concat', null, InputOption::VALUE_NONE, 'Concat the minified result to one file' , null), ); }
Запуск команды
Когда вызывается наш метод fire
нам нужно собрать наши аргументы и параметры. Мы можем сделать отдельную функцию, чтобы сделать это для нас:
private function init(){ // retrun an array $this->files = $this->argument('files'); // return a string $this->output_path = $this->argument('output'); // return true if passed, otherwise false $this->comments = $this->option('comments'); // return true if passed, otherwise false $this->concat = $this->option('concat'); }
Методы argument
и option
принимают ключ в качестве аргумента и возвращают соответствующее значение.
Чтобы наш пример был чистым и простым, мы будем использовать эту простую функцию с небольшой модификацией для процесса минимизации.
private function minify( $css, $comments ){ // Normalize whitespace $css = preg_replace( '/\s+/', ' ', $css ); // Remove comment blocks, everything between /* and */, unless preserved with /*! ... */ if( !$comments ){ $css = preg_replace( '/\/\*[^\!](.*?)\*\//', '', $css ); }//if // Remove ; before } $css = preg_replace( '/;(?=\s*})/', '', $css ); // Remove space after , : ; { } */ > $css = preg_replace( '/(,|:|;|\{|}|\*\/|>) /', '$1', $css ); // Remove space before , ; { } ( ) > $css = preg_replace( '/ (,|;|\{|}|\(|\)|>)/', '$1', $css ); // Strips leading 0 on decimal values (converts 0.5px into .5px) $css = preg_replace( '/(:| )0\.([0-9]+)(%|em|ex|px|in|cm|mm|pt|pc)/i', '${1}.${2}${3}', $css ); // Strips units if value is 0 (converts 0px to 0) $css = preg_replace( '/(:| )(\.?)0(%|em|ex|px|in|cm|mm|pt|pc)/i', '${1}0', $css ); // Converts all zeros value into short-hand $css = preg_replace( '/0 0 0 0/', '0', $css ); // Shortern 6-character hex color codes to 3-character where possible $css = preg_replace( '/#([a-f0-9])\\1([a-f0-9])\\2([a-f0-9])\\3/i', '#\1\2\3', $css ); return trim( $css ); }//minify
Теперь для обработки наших аргументов (файлов) мы собираемся создать отдельный метод для выполнения этой работы.
private function processFiles(){ // array of minified css $css_result = []; foreach ( $this->files as $file ) { //read file content $file_content = file_get_contents( $file ); //minify CSS and add it to the result array $css_result[] = $this->minify( $file_content, $this->comments ); }//foreach // if the concat flag is true if( $this->concat ){ // join the array of minified css $css_concat = implode( PHP_EOL, $css_result ); // save to file file_put_contents($this->output_path . '/all.min.css', $css_concat); }//if else{ foreach ($css_result as $key => $css) { //remove '.css' to add '.min.css' $filename = basename( $this->files[$key], '.css' ) . '.min.css'; // save to file file_put_contents($this->output_path . '/' . $filename, $css); }//for }//else }//processFiles
Наконец, наш метод fire
будет вызывать только два метода:
public function fire(){ $this->init(); $this->processFiles(); }
Совет: Вы также можете запустить внешнюю команду, используя метод call
.
$this->call('command:name', array('argument' => 'foo', '--option' => 'bar'));
Чтобы проверить нашу команду, мы собираемся скопировать некоторые CSS-файлы в наш каталог public/css
, а затем запустить команду.
php artisan cssmin 'public/css' 'public/css/style.css' 'public/css/responsive.css' php artisan cssmin 'public/css' 'public/css/style.css' 'public/css/responsive.css' --comments --concat
Первая команда создаст два файла ( style.min.css
, style.min.css
) в каталоге public/css
.
Поскольку мы использовали флаги --comments
и --concat
, мы получим файл all.min.css
содержащий два файла с оставленными комментариями.
Наша команда не очень наглядна и не дает никаких сообщений или уведомлений!
Улучшение команды
Прежде чем мы продолжим, в финальном репозитории GitHub я создам новый тег для нашей команды, чтобы вы могли переключаться и тестировать каждый из них.
Чтобы сделать команду немного многословной, Laravel предоставляет нам несколько функций вывода:
$this->line("This is a simple line message"); $this->info("This is an info"); $this->comment("This is a comment"); $this->question("This is a question"); $this->error("This is an error");
Это выведет:
Помимо отображения сообщений вы можете запросить информацию у пользователя, например:
$confirm = $this->confirm("'style.min.css' already exists, overwrite?", false); $filename = $this->ask("Save the file as?", 'all.min.css'); $choice = $this->choice( 'Please select a level of minication:', [ 'minify all', 'leave comments' ], 'default value' ); $password = $this->secret('Type your password to confirm:');
-
Метод
confirm
принимает два аргумента: сообщение с вопросом и значение по умолчанию, если пользователь вводит что-то отличное отy/n
. -
Метод
ask
будет запрашивать у пользователя ввод, а не толькоy/n
, и, если он оставлен пустым, возвращается значение по умолчанию. -
Метод
choice
предоставит пользователю нумерованный список для выбора, и если он останется пустым, будет возвращено значение по умолчанию. -
secret
метод подскажет пользователю вопрос и скроет ввод, но пользовательский ввод будет возвращен.
На самом деле, Laravel просто делает API-интерфейс консоли Symfony более простым и многословным, и есть еще много всего, что вам нужно.
Давайте сделаем нашу команду более многословной и будем держать пользователя в курсе выполненных задач.
private function processFiles(){ $css_result = []; foreach ( $this->files as $file ) { $this->comment("'{$file}'"); $this->info("Loading file"); //read file content $file_content = file_get_contents( $file ); $this->info("minifying"); //minify CSS and add it to the result array $css_result[] = $this->minify( $file_content, $this->comments ); }//foreach if( $this->concat ){ $this->comment("Concatenating into one file"); $css_concat = implode( PHP_EOL, $css_result ); $this->info("Saving to '{$this->output_path}/all.min.css'"); file_put_contents($this->output_path . '/all.min.css', $css_concat); }//if else{ foreach ($css_result as $key => $css) { //remove '.css' to add '.min.css' $filename = basename( $this->files[$key], '.css' ) . '.min.css'; $this->comment("Saving '{$filename}'"); file_put_contents($this->output_path . '/' . $filename, $css); }//for }//else }//processFiles
Наша функция теперь печатает несколько полезных сообщений, чтобы отслеживать, что происходит.
Примечание. Это будет помечено как v2
нашей команды в репозитории GitHub.
При создании приложения мы привыкли выводить список доступных маршрутов (маршруты php artisan routes
).
Symfony предоставляет функцию, которая позволяет легко распечатать такую таблицу. Проверьте документацию для примера. Далее мы увидим, как мы можем использовать некоторые помощники Symfony Console.
Использование помощников Symfony Console
Чтобы проиллюстрировать использование некоторых помощников Symfony, мы будем использовать помощник прогресса, чтобы постоянно информировать пользователя о ходе выполнения задания.
В конце нашего метода init
нам потребуется прогресс из HelperSet
, затем запустите наш индикатор выполнения.
$this->progressbar = $this->getHelperSet()->get('progress'); $this->progressbar->start($this->output, count($this->files) );
Метод start
принимает два аргумента, $this->output
является экземпляром ConsoleOuput
из консоли Symfony. Второй аргумент — это максимальное количество шагов.
Каждый раз, когда мы обрабатываем файл в нашем методе processFiles
мы продвигаем индикатор выполнения на один шаг, а когда работа завершается, мы заканчиваем индикатор выполнения и печатаем уведомление.
private function processFiles(){ $css_result = []; foreach ( $this->files as $file ) { //read file content $file_content = file_get_contents( $file ); //minify CSS and add it to the result array $css_result[] = $this->minify( $file_content, $this->comments ); // sleep for one second to see the effect //sleep(1); $this->progressbar->advance(); }//foreach if( $this->concat ){ $css_concat = implode( PHP_EOL, $css_result ); file_put_contents($this->output_path . '/all.min.css', $css_concat); }//if else{ foreach ($css_result as $key => $css) { //remove '.css' to add '.min.css' $filename = basename( $this->files[$key], '.css' ) . '.min.css'; file_put_contents($this->output_path . '/' . $filename, $css); }//for }//else $this->progressbar->finish(); $this->info('Done'); }//processFiles
Вы можете попробовать команду с несколькими файлами или раскомментировать строку функции sleep
чтобы увидеть живой эффект.
Примечание. Эта версия будет помечена как v3
в окончательном хранилище.
Вывод
В этой статье мы узнали, как создавать и расширять команды Laravel. В Laravel есть много встроенных команд, которые вы можете изучить, и вы также можете проверить наш окончательный репозиторий на GitHub, чтобы проверить конечный результат. Вопросов? Комментарии? Хотели бы вы увидеть больше учебных пособий по Artisan Command? Дайте нам знать!