• Время чтения ~5 мин
  • 31.08.2022

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

Последние несколько лет я сосредоточился на различных аспектах того, как Я пишу код и как я могу его улучшить. Я начал с изучения того, как сделать интеграцию с HTTP лучше и сделать ее более объектно-ориентированной.Я считаю, что нашел способ добиться этого, и теперь сосредоточил свое внимание на другом.

В некоторых случаях вы хотите работать с интерфейсом командной строки ОС в своих приложениях. Либо в веб-приложении, либо в другом приложении CLI. В прошлом мы использовали такие методы, как exec или passthru или shell_exec и system.Затем появился компонент Symfony Process, и мы были спасены.

Компонент процесса Symfony упростил интеграцию с процессами ОС и получение результата. Но то, как мы интегрируемся с этой библиотекой, все еще немного расстраивает. Мы создаем новый процесс, передавая массив аргументов, который делает команду, которую мы хотим запустить. Давайте посмотрим:

$command = new Process(
	command: ['git', 'push', 'origin', 'main'],
);
 
$command->run();

Что не так с этим подходом? Ну, если честно, ничего. Но есть ли способ улучшить опыт разработчиков? Допустим, мы переключаемся с git на svn (я знаю, что вряд ли).

Чтобы улучшить опыт разработчиков, во-первых, нам нужно понять компоненты, которые логически входят в создание команды ОС. Мы можем разбить их на:

executable
arguments

Наш исполняемый файл — это то, с чем мы взаимодействуем напрямую, например, php, git, brew или любой другой установленный двоичный файл в нашей системе. Затем аргументы заключаются в том, как мы можем взаимодействовать; это могут быть подкоманды, параметры, флаги или аргументы.

Итак, если мы немного абстрагируемся, у нас будет процесс и < code>команда, которая принимает аргументы.Мы будем использовать интерфейсы/контракты для определения наших компонентов, чтобы управлять тем, как должен работать наш рабочий процесс. Начнем с Контракта Процесса:

declare(strict_types=1);
 
namespace JustSteveKing\OS\Contracts;
 
use Symfony\Component\Process\Process;
 
interface ProcessContract
{
	public function build(): Process;
}

Мы говорим здесь, что каждый процесс должен иметь возможность сборки, и результатом созданного процесса должен быть Процесс Symfony. Наш процесс должен создать команду для запуска, поэтому теперь давайте посмотрим на наш контракт команды:

declare(strict_types=1);
 
namespace JustSteveKing\OS\Contracts;
 
interface CommandContract
{
	public function toArgs(): array;
}

Главное, чего мы хотим от нашей команды, — это иметь возможность возвращаться в качестве аргументов, которые мы можем передать в процесс Symfony как команду.

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

Сначала давайте создадим процесс Git, который реализует контракт процесса. которые мы только что описали:

class Git implements ProcessContract
{
	use HandlesGitCommands;
 
	private CommandContract $command;
}

Наш процесс реализует контракт и имеет свойство команды, которое мы будем использовать, чтобы обеспечить плавное построение и выполнение нашего процесса. У нас есть черта, которая позволит нам централизовать то, как создаются вещи для нашего процесса Git. Давайте посмотрим на это:

trait HandlesGitCommands
{
	public function build(): Process
	{
		return new Process(
			command: $this->command->toArgs(),
		);
	}
 
	protected function buildCommand(Git $type, array $args = []): void
	{
		$this->command = new GitCommand(
			type: $type,
			args: $args,
		);
	}
}

Итак, наш трейт показывает реализацию самого контракта процесса и дает инструкции о том, как должны быть построены процессы. Он также содержит метод, позволяющий нам абстрагироваться от команд построения.

Мы можем создать процесс и построить потенциальную команду до этого момента. Однако нам еще предстоит сделать команду. Мы создаем новую команду Git в трейте, которая использует класс Git для типа.Давайте посмотрим на другой класс Git, который является перечислением. Однако я покажу урезанную версию - как реально, вы хотите, чтобы это отображалось на все подкоманды git, которые вы хотите поддерживать:

enum Git: string
{
	case PUSH = 'push';
	case COMMIT = 'commit';
}

Затем мы пропускаем это через к команде Git:

final class GitCommand implements CommandContract
{
	public function __construct(
		public readonly Git $type,
		public readonly array $args = [],
		public readonly null|string $executable = null,
	) {
	}
 
	public function toArgs(): array
	{
		$executable = (new ExecutableFinder())->find(
			name: $this->executable ?? 'git',
		);
 
		if (null === $executable) {
			throw new InvalidArgumentException(
				message: "Cannot find executable for [$this->executable].",
			);
		}
 
		return array_merge(
			[$executable],
			[$this->type->value],
			$this->args,
		);
	}
}

В этом классе мы принимаем аргументы от нашего процесса, который в настоящее время обрабатывается нашим трейтом HandledGitCommands.Затем мы можем превратить это в аргументы, понятные процессу Symfony. Мы используем ExecutableFinder из пакета Symfony, чтобы минимизировать ошибки в путях. Однако мы также хотим генерировать исключение, если исполняемый файл не может быть найден.

Когда мы собираем все вместе внутри нашего процесса Git, это выглядит примерно так:

use JustSteveKing\OS\Commands\Types\Git as SubCommand;
 
class Git implements ProcessContract
{
	use HandlesGitCommands;
 
	private CommandContract $command;
 
	public function push(string $branch): Process
	{
		$this->buildCommand(
			type: SubCommand:PUSH,
			args: [
				'origin',
				$branch,
			],
		);
 
		return $this->build();
	}
}

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

$git = new Git();
$command = $git->push(
	branch: 'main',
);
 
$result = $command->run();

Результат метода push позволит вам взаимодействовать с процессом Symfony — это означает, что вы можете делать все что угодно с помощью команды с другой стороны. Единственное, что мы изменили, это построение объектно-ориентированной оболочки вокруг создания этого процесса.Это позволяет нам хорошо разрабатывать и сохранять контекст, а также расширять возможности тестируемым и расширяемым образом.

Как часто вы работаете с командами ОС в своих приложениях? Можете ли вы придумать какие-либо варианты использования для этого? Я опубликовал пример кода в репозитории на GitHub, чтобы вы могли использовать его и посмотреть, сможете ли вы улучшить интеграцию с вашей ОС. .

Отличным примером этого должен быть SSH, MySQL или даже ansible или terraform! Представьте, если бы вы могли эффективно запускать дампы MySQL по расписанию из Laravel artisan без постоянного использования сторонних пакетов!

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Об авторе CrazyBoy49z
WORK EXPERIENCE
Контакты
Ukraine, Lutsk
+380979856297