• Час читання ~5 хв
  • 31.08.2022

Іноді вам потрібно працювати з командами рівня ОС із програми PHP. Давайте подивимося, як ми можемо це зробити, і чи зможемо ми зробити роботу розробника кращою.

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

Існують випадки, коли ви хочете працювати з OS CLI у своїх програмах. У веб-програмі або іншій програмі CLI. У минулому ми використовували такі методи, як exec або passthru або shell_exec і system.Потім з’явився компонент Symfony Process, і ми були врятовані.

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

$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 Process. Ми використовуємо 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 без постійного використання пакетів сторонніх розробників!

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