분류 php

커맨드 라인의 PHP : 커맨드 컨트롤러 구현

컨텐츠 정보

  • 조회 291 (작성일 )

본문

소개 


MVC (Model, View, Controller) 패턴은 웹 응용 프로그램에서 매우 인기가 있습니다. 컨트롤러는 웹 애플리케이션에서 요청한 엔드 포인트를 기반으로 코드 실행을 처리합니다. 

CLI 애플리케이션에는 엔드 포인트가 없지만 명령 컨트롤러를 통해 명령 실행을 라우팅하여 유사한 워크 플로우를 구현할 수 있습니다.


원본 : https://dev.to/erikaheidi/php-in-the-command-line-implementing-command-controllers-13lh


이 시리즈의 첫 번째 자습서에서는 단일 진입 점을 사용하고 익명 함수를 통해 명령을 등록하여 CLI (명령 줄 인터페이스) 용 PHP 응용 프로그램을 부트 스트랩했습니다. 이 새로운 튜토리얼에서는 minicli를 리팩터링하여 명령 컨트롤러를 사용합니다.


이것은 Building Minicli 시리즈의 2 부입니다.


시작하기 전에 


이 튜토리얼을 따르려면 php-cli와 Composer가 필요합니다.


이 시리즈의 첫 번째 부분을 따르지 않은 경우 erikaheidi minicli 버전 0.1.0을 다운로드하여 설정을 부트스트랩 할 수 있습니다.


wget https://github.com/erikaheidi/minicli/archive/0.1.0.zip
unzip 0.1.0.zip
cd minicli


그런 다음 Composer를 실행하여 자동 로드를 설정하십시오. minicli에는 종속성이 없으므로 패키지를 설치하지 않습니다.


composer dump-autoload

다음을 사용하여 응용 프로그램을 실행하십시오.


php minicli


또는


chmod +x minicli
./minicli


1. CommandRegistry 클래스에 명령 등록 아웃소싱 


리팩토링을 시작하기 위해 애플리케이션에 대한 명령을 등록하고 찾는 작업을 처리 할 새 클래스를 작성합니다. 이 작업은 현재 App 클래스에서 처리하지만 CommandRegistry라는 클래스로 아웃소싱 합니다.


선택한 편집기를 사용하여 새 클래스를 만듭니다. 간단하게 하기 위해 이 튜토리얼에서는 nano를 사용합니다.


nano lib/CommandRegistry.php


다음 내용을 CommandRegistry 클래스에 복사하십시오.


<?php

namespace Minicli;

class CommandRegistry
{
    protected $registry = [];

    public function registerCommand($name, $callable)
    {
        $this->registry[$name] = $callable;
    }

    public function getCommand($command)
    {
        return isset($this->registry[$command]) ? $this->registry[$command] : null;
    }
}

참고 : getCommand 메소드는 삼항 연산자를 속기 if / else로 사용합니다. 명령을 찾을 수 없는 경우 널을 리턴합니다.


완료되면 파일을 저장하고 닫습니다.


이제 App.php 파일을 편집하고 명령을 등록하기 위한 CommandRegistry 클래스를 통합 한 다음 코드로 현재 컨텐츠를 바꾸십시오.


<?php

namespace Minicli;

class App
{
    protected $printer;

    protected $command_registry;

    public function __construct()
    {
        $this->printer = new CliPrinter();
        $this->command_registry = new CommandRegistry();
    }

    public function getPrinter()
    {
        return $this->printer;
    }

    public function registerCommand($name, $callable)
    {
        $this->command_registry->register($name, $callable);
    }

    public function runCommand(array $argv = [])
    {
        $command_name = "help";

        if (isset($argv[1])) {
            $command_name = $argv[1];
        }

        $command = $this->command_registry->getCommand($command_name);
        if ($command === null) {
            $this->getPrinter()->display("ERROR: Command \"$command_name\" not found.");
            exit;
        }

        call_user_func($command, $argv);
    }
}


./minicli를 사용하여 응용 프로그램을 지금 실행하는 경우 변경 사항이 없어야 하며 hello 및 help 명령을 모두 실행할 수 있습니다.


2. 명령 컨트롤러 구현 


이제 명령 리팩토링을 통해 특정 명령 프로 시저를 전용 CommandController 클래스로 옮깁니다.


2.1 CommandController 모델 만들기 


가장 먼저 해야 할 일은 여러 명령으로 상속 할 수 있는 추상 모델을 설정하는 것입니다. 이를 통해 우리는 몇 가지 기본 구현을 할 수 있을 뿐만 아니라 하위 (콘크리트) 클래스에 의해 구현되어야 하는 추상 메소드를 통해 일련의 기능을 시행 할 수 있습니다.


이 모델은 명령 행에서 사용자가 명령을 호출 할 때 주어진 구체적 CommandController에서 App 클래스가 호출 할 하나 이상의 필수 메소드를 정의해야 합니다.


텍스트 편집기에서 새 파일을 여십시오.


nano lib/CommandController.php

다음 내용을 이 파일에 복사하십시오. 초기 CommandController 추상 클래스는 다음과 같습니다.


<?php

namespace Minicli;

abstract class CommandController
{
    protected $app;

    abstract public function run($argv);

    public function __construct(App $app)
    {
        $this->app = $app;
    }

    protected function getApp()
    {
        return $this->app;
    }
}

CommandController에서 상속되는 모든 클래스는 getApp 메소드를 상속하지만 run 메소드를 구현하고 명령 실행을 처리해야 합니다.


2.2 콘크리트 명령 컨트롤러 생성 


이제 첫 번째 Command Controller 구체적 클래스인 HelloController를 만듭니다. 이 클래스는 hello 함수의 현재 정의를 익명 함수에서 CommandController 객체로 대체합니다.


Composer 파일 내에 프레임 워크용과 애플리케이션 용으로 각각 2 개의 별도 네임 스페이스를 만든 방법을 기억하십니까? 이 코드는 개발 중인 응용 프로그램에 따라 매우 다르므로 이제 App 네임 스페이스를 사용합니다.


먼저 app 네임 스페이스 디렉토리에 Command라는 새 폴더를 만듭니다.


mkdir app/Command


텍스트 편집기에서 새 파일을 여십시오.


nano app/Command/HelloController.php


다음 내용을 컨트롤러에 복사하십시오. 새로운 HelloController 클래스는 다음과 같습니다.


<?php

namespace App\Command;

use Minicli\CommandController;

class HelloController extends CommandController
{
    public function run($argv)
    {
        $name = isset ($argv[2]) ? $argv[2] : "World";
        $this->getApp()->getPrinter()->display("Hello $name!!!");
    }
}


여기에는 별거 아니에요 이전과 동일한 코드를 재사용 했지만 이제는 CommandController에서 상속 된 별도의 클래스에 배치되었습니다. App 객체는 이제 부모 추상 클래스 CommandController에서 상속 된 getApp 메소드를 통해 액세스 할 수 있습니다.


2.3 컨트롤러를 사용하도록 CommandRegistry 업데이트 


상속을 기반으로 명령 컨트롤러에 대한 간단한 아키텍처를 정의했지만 이러한 변경 사항을 처리하려면 CommandRegistry 클래스를 업데이트해야 합니다.


명령을 자체 클래스로 분리하는 기능은 유지 관리에 유용하지만 간단한 명령의 경우 익명 함수를 사용하는 것을 선호 할 수 있습니다.


다음 코드는 익명 함수를 사용하여 명령을 정의하는 이전 방법과 호환되는 방식으로 명령 컨트롤러 등록을 구현합니다. 선택한 편집기를 사용하여 CommandRegistry.php 파일을 여십시오.


nano lib/CommandRegistry.php


다음 코드를 사용하여 CommandRegistry 클래스의 현재 컨텐츠를 업데이트하십시오.


<?php

namespace Minicli;

class CommandRegistry
{
    protected $registry = [];

    protected $controllers = [];

    public function registerController($command_name, CommandController $controller)
    {
        $this->controllers = [ $command_name => $controller ];
    }

    public function registerCommand($name, $callable)
    {
        $this->registry[$name] = $callable;
    }

    public function getController($command)
    {
        return isset($this->controllers[$command]) ? $this->controllers[$command] : null;
    }

    public function getCommand($command)
    {
        return isset($this->registry[$command]) ? $this->registry[$command] : null;
    }

    public function getCallable($command_name)
    {
        $controller = $this->getController($command_name);

        if ($controller instanceof CommandController) {
            return [ $controller, 'run' ];
        }

        $command = $this->getCommand($command_name);
        if ($command === null) {
            throw new \Exception("Command \"$command_name\" not found.");
        }

        return $command;
    }
}


이제 응용 프로그램 내에 등록 된 명령 컨트롤러와 간단한 콜백 함수가 모두 있으므로 명령을 호출 할 때 어떤 코드를 호출해야 하는지 getCallable이라는 메서드를 구현했습니다. 이 메소드는 명령을 찾을 수없는 경우 예외를 발생 시킵니다. 우리가 그것을 구현 한 방식으로, 커맨드 컨트롤러는 항상 익명 함수를 통해 등록 된 단일 커맨드 보다 우선합니다.


이전 코드 교체가 완료되면 파일을 저장하고 닫습니다.


2.4 앱 클래스 업데이트 


최근의 모든 변경 사항을 처리하려면 여전히 App 클래스를 업데이트 해야 합니다.


App 클래스가 포함 된 파일을 엽니다.


nano lib/App.php


App.php 파일의 현재 내용을 다음 코드로 바꿉니다 :


<?php

namespace Minicli;

class App
{
    protected $printer;

    protected $command_registry;

    public function __construct()
    {
        $this->printer = new CliPrinter();
        $this->command_registry = new CommandRegistry();
    }

    public function getPrinter()
    {
        return $this->printer;
    }

    public function registerController($name, CommandController $controller)
    {
        $this->command_registry->registerController($name, $controller);
    }

    public function registerCommand($name, $callable)
    {
        $this->command_registry->registerCommand($name, $callable);
    }

    public function runCommand(array $argv = [], $default_command = 'help')
    {
        $command_name = $default_command;

        if (isset($argv[1])) {
            $command_name = $argv[1];
        }

        try {
            call_user_func($this->command_registry->getCallable($command_name), $argv);
        } catch (\Exception $e) {
            $this->getPrinter()->display("ERROR: " . $e->getMessage());
            exit;
        }
    }
}

먼저 App 객체를 인스턴스화 한 후 사용자가 Command Controller를 등록 할 수 있는 방법 인 registerController를 구현했습니다. 

이 메소드는 명령 등록을 CommandRegistry 오브젝트에 아웃소싱 합니다. 

그런 다음 try / catch 블록에서 가능한 예외를 포착하여 getCallable을 사용하도록 runCommand 메소드를 업데이트했습니다.


편집이 끝나면 파일을 저장하고 닫습니다.


2.5 HelloController 명령 컨트롤러 등록 


minicli 스크립트는 여전히 익명 함수를 통해 명령을 정의하는 기존 방법을 사용하고 있습니다. 이제 새로운 HelloController 명령 컨트롤러를 사용하도록 이 파일을 업데이트하지만 이전과 같은 방식으로 도움말 명령 등록을 익명 함수로 등록합니다.


minicli 스크립트를 엽니다 :


nano minicli


업데이트 된 minicli 스크립트는 다음과 같습니다.


#!/usr/bin/php
<?php

if (php_sapi_name() !== 'cli') {
    exit;
}

require __DIR__ . '/vendor/autoload.php';

use Minicli\App;

$app = new App();

$app->registerController('hello', new \App\Command\HelloController($app));

$app->registerCommand('help', function (array $argv) use ($app) {
    $app->getPrinter()->display("usage: minicli hello [ your-name ]");
});

$app->runCommand($argv);


새 코드로 파일을 업데이트 한 후에는 이전과 동일한 방식으로 응용 프로그램을 실행할 수 있어야 하며 정확히 동일하게 작동해야 합니다.


./minicli


차이점은 이제 registerCommand를 사용하여 익명 함수를 등록하거나 CommandController에서 상속되는 Controller 클래스를 작성하여 명령을 작성하는 두 가지 방법이 있다는 것입니다. Controller 클래스를 사용하면 코드를 보다 체계적이고 유지 관리 할 수 ​​있지만 빠른 해킹과 간단한 스크립트를 위해 익명 함수와 함께 "짧은 방법"을 사용할 수 있습니다.


결론 및 다음 단계 


이 게시물에서는 명령 컨트롤러를 사용하는 아키텍처와 함께 클래스에 정의 된 명령을 지원하도록 minicli를 리팩토링 했습니다. 현재로서는 잘 작동하고 있지만 Controller는 둘 이상의 명령을 처리 할 수 ​​있어야 합니다. 이를 통해 다음과 같은 명령 구조를 보다 쉽게 ​​구현할 수 있습니다.


command [ subcommand ] [ action ] [ params ]
command [ subcommand 1 ] [ subcommand n ] [ params ]