使用 Laravel Zero 创建无忧 CLI 应用程序

最后更新于 作者:

Zero Hassle CLI Application with Laravel Zero image

怎么说呢?CLI 应用程序很酷。能够在任何地方打开终端,只需运行一条命令就可以完成一项可能需要更长时间才能完成的任务。打开浏览器,访问正确的页面,登录并找到你需要做的事情,等待页面加载... 你懂的。

在过去的几年里,命令终端得到了大量的投入;从 ZSH 到自动完成,从 FIG 到 Warp - CLI 是我们无法逃避的东西。我构建 CLI 应用程序是为了帮助我更有效地完成小任务,或者按计划完成工作。

每当我查看任何与 Laravel 相关的在线内容时,它总是与 Web 应用程序有关,而且这是有道理的。毕竟,Laravel 是一个很棒的 Web 应用程序框架!但是,利用我们对 Laravel 的热爱,也可以用于 CLI 应用程序。现在,我们可以使用完整的 Laravel 安装并运行调度器来运行我们需要的 Artisan 命令 - 但这有时会显得过于复杂。如果你不需要 Web 界面,你就不需要 Laravel。相反,让我们谈谈 Laravel Zero,这是 Nuno Maduro 的另一个杰作。

Laravel Zero 将自己描述为“用于控制台应用程序的微型框架” - 这很准确。它允许你使用经过验证的框架构建 CLI 应用程序 - 比使用像 Laravel 这样的东西更小巧。它文档齐全,健壮且积极维护 - 使其成为构建任何 CLI 应用程序的完美选择。

在本教程中,我将带你逐步完成一个使用 Laravel Zero 的简单示例,希望它能让你看到它有多么有用。我们将构建一个 CLI 应用程序,使我们能够查看我的 Todoist 帐户中的项目和任务,这样我就不必打开应用程序或 Web 浏览器。

首先,我们需要转到 Todoist 的 Web 应用程序并打开集成设置以获取我们的 API 令牌。我们稍后会用到它。第一步是创建一个新的 Laravel Zero 项目,我们可以使用它。

composer create-project --prefer-dist laravel-zero/laravel-zero todoist

在你的 IDE 中打开这个新项目,以便我们开始构建我们的 CLI 应用程序。我们知道的第一件事是,我们想要存储我们的 API 令牌,因为我们不想每次想要运行新命令时都必须粘贴它。这里的一种典型方法是将 API 令牌存储在用户的 home 目录中,位于隐藏目录中的配置文件中。所以我们将看看如何实现这一点。

我们想要创建一个 ConfigurationRepository,它允许我们使用本地文件系统来获取和设置我们在 CLI 应用程序中可能需要的任何值。与我编写的多数代码一样,我将创建一个接口/契约,以便在需要将此更改为与其他文件系统一起使用时绑定实现。

declare(strict_types=1);
 
namespace App\Contracts;
 
interface ConfigurationContract
{
public function all(): array;
 
public function clear(): ConfigurationContract;
 
public function get(string $key, mixed $default = null): array|int|string|null;
 
public function set(string $key, array|int|string $value): ConfigurationContract;
}

现在我们知道了它应该做什么,我们可以看看本地文件系统的实现。

declare(strict_types=1);
 
namespace App\Repositories;
 
use App\Contracts\ConfigurationContract;
use App\Exceptions\CouldNotCreateDirectory;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\File;
 
final class LocalConfiguration implements ConfigurationContract
{
public function __construct(
protected readonly string $path,
) {}
 
public function all(): array
{
if (! is_dir(dirname(path: $this->path))) {
if (! mkdir(
directory: $concurrentDirectory = dirname(
path: $this->path,
),
permissions: 0755,
recursive: true
) && !is_dir(filename: $concurrentDirectory)) {
throw new CouldNotCreateDirectory(
message: "Directory [$concurrentDirectory] was not created",
);
}
}
 
if (file_exists(filename: $this->path)) {
return json_decode(
json: file_get_contents(
filename: $this->path,
),
associative: true,
depth: 512,
flags: JSON_THROW_ON_ERROR,
);
}
 
return [];
}
 
public function clear(): ConfigurationContract
{
File::delete(
paths: $this->path,
);
 
return $this;
}
 
public function get(string $key, mixed $default = null): array|int|string|null
{
return Arr::get(
array: $this->all(),
key: $key,
default: $default,
);
}
 
public function set(string $key, array|int|string $value): ConfigurationContract
{
$config = $this->all();
 
Arr::set(
array: $config,
key: $key,
value: $value,
);
 
file_put_contents(
filename: $this->path,
data: json_encode(
value: $config,
flags: JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT,
),
);
 
return $this;
}
}

我们使用 Laravel 中的一些辅助方法和一些基本的 PHP 来获取内容和检查文件 - 然后在需要时读取和写入内容。有了它,我们可以管理本地文件系统中任何位置的文件。我们的下一步是将它绑定到我们的容器中,以便我们可以设置当前的实现以及我们想要如何从容器中解析它。

declare(strict_types=1);
 
namespace App\Providers;
 
use App\Contracts\ConfigurationContract;
use App\Repositories\LocalConfiguration;
use Illuminate\Support\ServiceProvider;
 
final class AppServiceProvider extends ServiceProvider
{
public array $bindings = [
ConfigurationContract::class => LocalConfiguration::class,
];
 
public function register(): void
{
$this->app->singleton(
abstract: LocalConfiguration::class,
concrete: function (): LocalConfiguration {
$path = isset($_ENV['APP_ENV']) && $_ENV['APP_ENV'] === 'testing'
? base_path(path: 'tests')
: ($_SERVER['HOME'] ?? $_SERVER['USERPROFILE']);
 
return new LocalConfiguration(
path: "$path/.todo/config.json",
);
},
);
}
}

我们使用服务提供者的 bindings 属性将我们的契约绑定到我们的实现。然后在 register 方法中,我们设置了我们想要如何构建实现。现在,当我们将 ConfigurationContract 注入到一个命令中时,我们将获得一个 LocalConfiguration 的实例,该实例已作为单例解析。

现在我们要做的第一件事是为 Laravel Zero 应用程序命名,以便我们可以使用与我们正在构建的内容相关的名称来调用 CLI 应用程序。我将我的应用程序命名为“todo”。

php application app:rename todo

现在,我们可以使用 php todo ... 来调用我们的命令,并开始构建我们想要使用的 CLI 命令。在构建命令之前,我们需要创建一个与 Todoist API 集成的类。同样,如果我决定从 Todoist 切换到其他提供商,我将为它创建一个接口/契约。

declare(strict_types=1);
 
namespace App\Contracts;
 
interface TodoContract
{
public function projects(): ResourceContract;
 
public function tasks(): ResourceContract;
}

我们有两个方法,projectstasks,它们将为我们返回一个资源类。像往常一样,这个资源类需要一个契约。资源契约将使用数据对象契约,但与其创建它,我将使用我构建在我的一个包中的一个契约。

composer require juststeveking/laravel-data-object-tools

现在我们可以创建资源契约本身了。

declare(strict_types=1);
 
namespace App\Contracts;
 
use Illuminate\Support\Collection;
use JustSteveKing\DataObjects\Contracts\DataObjectContract;
 
interface ResourceContract
{
public function list(): Collection;
 
public function get(string $identifier): DataObjectContract;
 
public function create(DataObjectContract $resource): DataObjectContract;
 
public function update(string $identifier, DataObjectContract $payload): DataObjectContract;
 
public function delete(string $identifier): bool;
}

这些是资源本身的基本 CRUD 选项,命名得很贴切。当然,如果我们想要一个更易于访问的 API,我们可以在实现中扩展它。现在让我们开始构建 Todoist 实现。

declare(strict_types=1);
 
namespace App\Services\Todoist;
 
use App\Contracts\ResourceContract;
use App\Contracts\TodoContract;
use App\Services\Todoist\Resources\ProjectResource;
use App\Services\Todoist\Resources\TaskResource;
 
final class TodoistClient implements TodoContract
{
public function __construct(
public readonly string $url,
public readonly string $token,
) {}
 
public function projects(): ResourceContract
{
return new ProjectResource(
client: $this,
);
}
 
public function tasks(): ResourceContract
{
return new TaskResource(
client: $this,
);
}
}

我将在 GitHub 上发布此项目,以便你查看完整的可运行示例。

我们的 TodoistClient 将返回一个新的 ProjectResource 实例,并将我们客户端的实例传递给构造函数,以便我们可以访问 URL 和令牌,这就是这些属性受保护而不是私有的原因。

让我们看看我们的 ProjectResource 将是什么样子。然后我们可以逐步了解它的工作原理。

declare(strict_types=1);
 
namespace App\Services\Todoist\Resources;
 
use App\Contracts\ResourceContract;
use App\Contracts\TodoContract;
use Illuminate\Support\Collection;
use JustSteveKing\DataObjects\Contracts\DataObjectContract;
 
final class ProjectResource implements ResourceContract
{
public function __construct(
private readonly TodoContract $client,
) {}
 
public function list(): Collection
{
// TODO: Implement list() method.
}
 
public function get(string $identifier): DataObjectContract
{
// TODO: Implement get() method.
}
 
public function create(DataObjectContract $resource): DataObjectContract
{
// TODO: Implement create() method.
}
 
public function update(string $identifier, DataObjectContract $payload): DataObjectContract
{
// TODO: Implement update() method.
}
 
public function delete(string $identifier): bool
{
// TODO: Implement delete() method.
}
}

一个相当简单的结构,很好地遵循了我们的接口/契约。现在我们可以开始考虑如何构建请求并发送它们。我喜欢这样做,你可以随意用不同的方式去做,就是创建一个我的资源用来 send 请求的 Trait。然后,我可以在 ResourceContract 上设置这个新的 send 方法,以便资源要么使用该 Trait,要么必须实现自己的 send 方法。Todoist API 有几种资源,所以在这个 Trait 中共享这种行为更有意义。让我们看看这个 Trait。

declare(strict_types=1);
 
namespace App\Services\Concerns;
 
use App\Exceptions\TodoApiException;
use App\Services\Enums\Method;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;
 
trait SendsRequests
{
public function send(
Method $method,
string $uri,
null|array $data = null,
): Response {
$request = $this->makeRequest();
 
$response = $request->send(
method: $method->value,
url: $uri,
options: $data ? ['json' => $data] : [],
);
 
if ($response->failed()) {
throw new TodoApiException(
response: $response,
);
}
 
return $response;
}
 
protected function makeRequest(): PendingRequest
{
return Http::baseUrl(
url: $this->client->url,
)->timeout(
seconds: 15,
)->withToken(
token: $this->client->token,
)->withUserAgent(
userAgent: 'todo-cli',
);
}
}

相关文章:[PHP 用户代理解析器[(https://news.laravel.net.cn/php-desktop-and-mobile-user-agent-parser)

我们有两个方法,一个用于构建请求,一个用于发送请求 - 因为我们想要一种标准的方式来执行这两个操作。现在,让我们在 ResourceContract 上添加 send 方法,以强制在所有提供商中使用这种方法。

declare(strict_types=1);
 
namespace App\Contracts;
 
use App\Services\Enums\Method;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Collection;
use JustSteveKing\DataObjects\Contracts\DataObjectContract;
 
interface ResourceContract
{
public function list(): Collection;
 
public function get(string $identifier): DataObjectContract;
 
public function create(DataObjectContract $resource): DataObjectContract;
 
public function update(string $identifier, DataObjectContract $payload): DataObjectContract;
 
public function delete(string $identifier): bool;
 
public function send(
Method $method,
string $uri,
null|array $data = null,
): Response;
}

现在,我们的资源要么必须创建自己的创建和发送请求的方式,要么可以实现这个 Trait。正如你从代码示例中看到的,我为请求方法创建了一个辅助 Enum - 此代码在存储库中,所以你可以随意深入查看代码以获取更多信息。

在我们深入集成方面之前,我们可能应该创建一个用于登录的命令。毕竟,本教程是关于 Laravel Zero 的!

使用以下命令在你的终端中创建一个新的命令。

php todo make:command Todo/LoginCommand

此命令将需要获取 API 令牌并将其存储在配置存储库中,以便在将来的命令中使用。让我们看看这个命令是如何工作的。

declare(strict_types=1);
 
namespace App\Commands\Todo;
 
use App\Contracts\ConfigurationContract;
use LaravelZero\Framework\Commands\Command;
 
final class LoginCommand extends Command
{
protected $signature = 'login';
 
protected $description = 'Store your API credentials for the Todoist API.';
 
public function handle(ConfigurationContract $config): int
{
$token = $this->secret(
question: 'What is your Todoist API token?',
);
 
if (! $token) {
$this->warn(
string: "You need to supply an API token to use this application.",
);
 
return LoginCommand::FAILURE;
}
 
$config->clear()->set(
key: 'token',
value: $token,
)->set(
key: 'url',
value: 'https://api.todoist.com/rest/v1',
);
 
$this->info(
string: 'We have successfully stored your API token for Todoist.',
);
 
return LoginCommand::SUCCESS;
}
}

我们将 ConfigurationContract 注入到 handle 方法中,它将为我们解析配置。然后,我们请求一个 API 令牌作为秘密,这样它就不会在用户键入时显示在他们的终端上。在清除任何当前值后,我们可以使用配置来设置令牌和 URL 的新值。

一旦我们能够进行身份验证,我们就可以创建一个额外的命令来列出我们的项目。让我们现在创建它。

php todo make:command Todo/Projects/ListCommand

此命令将需要使用 TodoistClient 来获取所有项目并在表格中列出它们。让我们看看它是什么样子的。

declare(strict_types=1);
 
namespace App\Commands\Todo\Projects;
 
use App\Contracts\TodoContract;
use App\DataObjects\Project;
use LaravelZero\Framework\Commands\Command;
use Throwable;
 
final class ListCommand extends Command
{
protected $signature = 'projects:list';
 
protected $description = 'List out Projects from the Todoist API.';
 
public function handle(
TodoContract $client,
): int {
try {
$projects = $client->projects()->list();
} catch (Throwable $exception) {
$this->warn(
string: $exception->getMessage(),
);
 
return ListCommand::FAILURE;
}
 
$this->table(
headers: ['ID', 'Project Name', 'Comments Count', 'Shared', 'URL'],
rows: $projects->map(fn (Project $project): array => $project->toArray())->toArray(),
);
 
return ListCommand::SUCCESS;
}
}

如果你查看 GitHub 仓库中的代码,你会发现 ProjectResource 上的 list 命令返回了一个 Project 数据对象的集合。这使我们能够映射集合中的每个项目,将对象转换为数组,并将集合作为数组返回,这样我们就可以轻松地以表格格式查看我们拥有的项目。使用合适的终端,我们还可以点击项目的 URL,在浏览器中打开它,如果需要的话。

如你从以上方法中看到的那样,使用 Laravel Zero 构建 CLI 应用程序非常简单 - 你能构建的唯一限制是你的想象力。

正如本教程中提到的,你可以在 GitHub 仓库在线查看,因此你可以克隆完整的示例。

Steve McDougall photo

Laravel 新闻 的技术作家,Treblle 的开发者倡导者。API 专家,资深 PHP/Laravel 工程师。YouTube 直播主

Cube

Laravel 新闻

加入 40,000 多位其他开发者,不错过任何新技巧、教程等。

Laravel Forge logo

Laravel Forge

轻松创建和管理你的服务器,并在几秒钟内部署你的 Laravel 应用程序。

Laravel Forge
Tinkerwell logo

Tinkerwell

Laravel 开发者必备的代码运行器。使用 AI、自动补全和对本地和生产环境的即时反馈来调试代码。

Tinkerwell
No Compromises logo

不妥协

Joel 和 Aaron,来自不妥协播客的两位经验丰富的开发者,现在可以为你的 Laravel 项目雇佣。 ⬧ 固定价格 $7500/月。 ⬧ 没有冗长的销售流程。 ⬧ 没有合同。 ⬧ 100% 退款保证。

不妥协
Kirschbaum logo

Kirschbaum

提供创新和稳定性,以确保你的 Web 应用程序取得成功。

Kirschbaum
Shift logo

Shift

正在运行旧版本的 Laravel?即时、自动的 Laravel 升级和代码现代化,以保持你的应用程序新鲜。

Shift
Bacancy logo

Bacancy

只需 $2500/月,就可以让你的项目与经验丰富的 Laravel 开发者(拥有 4-6 年经验)合作。获得 160 小时专业知识和 15 天无风险试用期。现在就预约电话!

Bacancy
Lucky Media logo

Lucky Media

现在就获得幸运 - Laravel 开发的理想选择,拥有十多年的经验!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel 电子商务

Laravel 的电子商务。一个开源软件包,它将现代无头电子商务功能的力量带入 Laravel。

Lunar: Laravel 电子商务
LaraJobs logo

LaraJobs

官方 Laravel 工作平台

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS 启动套件

SaaSykit 是一个 Laravel SaaS 启动套件,包含运行现代 SaaS 所需的所有功能。支付、漂亮的结账页面、管理面板、用户仪表板、身份验证、现成的组件、统计数据、博客、文档等等。

SaaSykit: Laravel SaaS 启动套件
Rector logo

Rector

你无缝升级 Laravel 的合作伙伴,降低成本,加速创新,帮助公司取得成功

Rector
MongoDB logo

MongoDB

使用 MongoDB 和 Laravel 的强大集成来增强你的 PHP 应用程序,使开发人员能够轻松高效地构建应用程序。支持事务、搜索、分析和移动用例,同时使用熟悉的 Eloquent API。了解 MongoDB 的灵活、现代数据库如何改变你的 Laravel 应用程序。

MongoDB
Maska is a Simple Zero-dependency Input Mask Library image

Maska 是一个简单的零依赖输入掩码库

阅读文章
Add Swagger UI to Your Laravel Application image

在你的 Laravel 应用程序中添加 Swagger UI

阅读文章
Assert the Exact JSON Structure of a Response in Laravel 11.19 image

在 Laravel 11.19 中断言响应的精确 JSON 结构

阅读文章
Build SSH Apps with PHP and Laravel Prompts image

使用 PHP 和 Laravel 提示构建 SSH 应用程序

阅读文章
Building fast, fuzzy site search with Laravel and Typesense image

使用 Laravel 和 Typesense 构建快速、模糊的网站搜索

阅读文章
Add Comments to your Laravel Application with the Commenter Package image

使用 Commenter 包为你的 Laravel 应用程序添加评论

阅读文章