承接 devdot/cli 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

devdot/cli

Composer 安装命令:

composer require devdot/cli

包简介

Tools for CLI projects, on top of symfony/console.

README 文档

README

Tools for CLI projects, on top of symfony/console.

Features

Installation

Get started with a new project:

composer create-project devdot/cli-project

Or install the framework into an existing project like this:

composer require devdot/cli
composer require devdot/cli-builder --dev
vendor/bin/cli-builder init

Documentation

CLI Builder

Using devdot/cli-builder, cli applications can be built in minutes.

Create new commands like this:

vendor/bin/cli-builder make:command Testing/Example

This will create a new command class at src/Commands/Testing/Example that is wired to testing:example. This generated class will be fully registered in you application and it extends your projects's custom base command class:

namespace App\Commands\Testing;

use App\Commands\Command;

class Example extends Command
{
    protected function handle(): int
    {
        //

        return self::SUCCESS;
    }
}

Run vendor/bin/cli-builder list to see an overview of available commands.

Application Layout

Take a look at devdot/cli-project to see the default application layout.

⊢ bin/
    ⊢ build                     // build script for production
    ⊢ dev                       // application in development mode
    ⊢ prod                      // application in production mode
⊢ src/
    ⊢ Commands/
        ⊢ Example/
            ⊢ SendMessage.php   // command "example:send-message"
        ⊢ About.php             // command "about"
        ⊢ Command.php           // project base command class (optional)
    ⊢ Kernel.php                // application kernel, called by binaries

Binaries

If you want to provide your production application through your composer project package, you may call cli-builder composer:add-binary or add it to composer.json yourself, (see more).

The provided binaries are described as follows:

Binary cli-builder command Description
bin/dev run:dev Execute your application in development mode. The container will be rebuild on every call, by default no caching is active.
bin/prod run:prod Execute your application in production mode. This will assume a built container and use all available caching.
bin/build run:build <version> <name> Build and cache the production container. This is required for autowiring to work in production.

For more details on the build process, see Production Build.

File based routing

Command names are generated automatically based on their location in the App\\Commands namespace. See examples for generated names:

Command Path
about src/Commands/About.php
load-config src/Commands/LoadConfig.php
cache:clear src/Commands/Cache/Clear.php
app-config:reload-data src/Commands/AppConfig/ReloadData.php
routing:manager:reset src/Commands/Routing/Manager/Reset.php

Upon building the container, all classes in the namespace App\\Commands that are commands (extending Devdot\\Cli\\Command) are added to the CLI application. No manual registration is necessary, command names do not need to be set. Constructors and Trait Constructors are autowired with the container.

If you want to change a commands name, you may do so like this:

class SomeCustomCommand extends Command
{
    public static function getGeneratedName(): string
    {
        return 'custom_command_name';
    }
}

If you want to use a new or changed command in the production application, make sure to run bin/build first. The container will store command names (Command::getGeneratedName) and cache them for production.

To increase performance, command classes will only be created when necessary (by using Symfony's ContainerCommandLoader). Therefore, __construct and configure are only called when the command is executed or when the commands are list-ed.

Exceptions as Exit Codes

Exceptions may be used to find a direct exit from nested control flow. Any uncaught exception during Command::handle will cause the process to exit with the exception's error code. The exception message will be displayed (the stack trace is only shown in development).

You may use exceptions as exit codes like this:

namespace App\Commands;

use Devdot\Cli\Exceptions\CommandFailedException;

class ExampleCommand extends Command
{
    protected function handle(): int
    {
        $this->handleReadingFiles();
        $this->handleProcessingFiles()
        $this->handleWritingFiles();

        return self::SUCCESS;
    }

    private function handleReadingFiles(): void
    {
        if ($this->fileReader->get() === '') {
            // exit the command
            throw new CommandFailedException('File is empty'); // the command will return code 1
        }
    }

    // ...
}

Kernel

The Kernel.php file is required as it anchors the container in your namespace and allows auto-detection of commands.

The container is invoked by binaries through Kernel::run, with the first param set to true on development builds (which will build a fresh container on every call).

You may change the application name and version by overwriting Kernel::getName and Kernel::getVersion respectively. By default, the name is assumed from the directory name and the version is pulled from the latest git tag.

Services for the container may be added as class-string in the Kernel::$services array. For more on services and providers, see Service Providers.

Command methods for overwriting

There are some methods in the Command class that are ready for being overwritten by extending classes:

class ExampleCommand extends Command
{
    protected function beforeHandle(): void
    {
    }

    protected function afterHandle(int $code): void
    {
    }

    protected function handleException(Throwable $t): int
    {
        if ($t instanceof MyException) {
            //
        }

        return parent::handleException($t);
    }
}

Customization

Follow these steps to change the default namespace or directory:

  • change namespace in composer.json
  • regenerate the Kernel using cli-builder make:kernel
  • change namespace in entrypoints (files in bin)
  • dump composer autoload

You may use the cli-builder to automatically rename the namespace:

vendor/bin/cli-builder composer:rename-namespace New\\Namespace

Traits

There are some traits that provide common functionality to commands as you require them. Thanks to Trait Constructors, they require no setup in Command::__construct or Command::configure.

Trait Description
Devdot\Cli\Traits\ForceTrait Adds--force (-f) option to a command. Check input like this: $this->input->getOption('force').
Devdot\Cli\Traits\RunProcessTrait Add theCommand::runProcess utility. Execute on the command line or call other internal commands with a nice wrapper. Works on top of symfony/process.

Container

All parts of a devdot/cli application are connected through the container's dependency injection. The container registers commands and constructs commands, and autowires all services as needed.

Register a service in through the Kernel and have it automatically injected in your command:

final class Kernel extends BaseKernel
{
    protected array $services = [
        // register your service classes here
        \App\Services\YourService::class,
    ];

    // ...
}

class ExampleCommand extends Command
{
    public function __construct(
        private \App\Services\YourService $service, // automatically injected
    ) {
        // ...
    }

    protected function handle(): int
    {
        $this->service->doSomething();

        // ...
    }
}

Services are autowired to other services, so you may use dependency injection on service constructors too.

The container is build before commands are created. In production mode, the container is only loaded from cache. Therefore, unused services cause no overhead in production. See more at Production Build.

Service Providers

If you have services that require complex setup, you need to use a service provider. Create a new service provider using cli-builder make:provider YourProvider and add it to Kernel::$providers.

// src/Providers/YourProvider.php
namespace App\Providers;

use Devdot\Cli\Container\ServiceProvider;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class YourProvider extends ServiceProvider
{
    /** @var class-string[] */
    protected array $services = [
        // you may add additional service class names here
    ];

    public function booting(ContainerBuilder $container): void
    {
        parent::booting($container); // this will register the services in $this->services

        // this is called during the container build phase

        $container->autowire(SomeService::class)->setFactory([SomeFactory::class, 'make']);
    }
}


// src/Kernel.php
namespace App;

final class Kernel extends Devdot\Cli\Kernel
{
    /** @var class-string<\Devdot\Cli\Container\ServiceProvider>[] */
    protected array $providers = [
        // add the new provider here
        \App\Providers\YourProvider::class,
    ];

    // ...
}

In production mode, service providers are never invoked, because the entire container is cached (see more at Production Build).

The Kernel acts as a service provider too, so you may overwrite Kernel::booting just like in a regular service provider.

Command Trait Constructors

Traits that are used in commands may have their own constructors that are called automatically. Trait constructors can be used to setup the command:

namespace Devdot\Cli\Traits;

trait ForceTrait
{
    public function __constructForceTrait(): void
    {
        // this method will be called by the container after Command::__construct on each command that uses this trait
        $this->addOption('force', 'f', null, 'Force run the command.');
    }
}

Trait constructors can receive services through dependency injection like this:

trait DataTrait
{
    protected Data $data
    public function __constructForceTrait(Data $data): void
    {
        // $data will be provided by the container if the service Data is registered
        $this->data = $data;
    }
}

Trait constructors are called on the commands that implement those traits. Traits may be inherited both by other traits or in the command classes.

Production Build

It is recommended to deploy the application based on the production binary. Use bin/build to build the container that is used in production.

Some important hints:

  • Command names are generated through Command::getGeneratedName and cached in the container. Dynamic names will not work in production.
  • Application name and version are cached in the container and will not behave dynamically in production.
  • Unused services will be optimized out of the production container. Only accessing them without dependency injection (i.e. through $container->get('service') may cause them to be unavailable in production.
  • Service Providers are only called during the container build process. Therefore, these classes will not even be initialized in production.

By default, the cached container is stored in src/ProductionContainer.php. The class can be changed through overwriting const Kernel::CACHED_CONTAINER_NAME.

You may use cli-builder build:phar to create an executable PHAR from your production application.

devdot/cli 适用场景与选型建议

devdot/cli 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 53 次下载、GitHub Stars 达 0, 最近一次更新时间为 2024 年 08 月 15 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 devdot/cli 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 devdot/cli 我们能提供哪些服务?
定制开发 / 二次开发

基于 devdot/cli 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 53
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 7
  • 依赖项目数: 3
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 1
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2024-08-15