provisionesta / gitlab-api-client
Laravel 的 GitLab API 客户端
Requires
- php: ^8.0
- illuminate/config: ^8.0 || ^9.0 || ^10.0 || ^11.0
- illuminate/http: ^8.0 || ^9.0 || ^10.0 || ^11.0
- illuminate/log: ^8.0 || ^9.0 || ^10.0 || ^11.0
- illuminate/support: ^8.0 || ^9.0 || ^10.0 || ^11.0
- nesbot/carbon: ^2.67 || ^3.0
- provisionesta/audit: ^1.1
Requires (Dev)
- larastan/larastan: ^2.7
- orchestra/testbench: ^6.23 || ^7.0 || ^8.0
Suggests
- provisionesta/gitlab-laravel-actions: Collection of Laravel Actions API/Console/Job/Service CRUD classes for GitLab users, groups, projects, epics, issues, merge requests, repository files, and other endpoints.
README
[[目录]]
概述
GitLab API 客户端是一个开源的 Composer 包,用于在 Laravel 应用程序中连接到 GitLab SaaS 或自托管实例,以提供用户、组、项目和其他相关功能的配置和撤销配置。
该包由开源社区维护,不由任何公司维护。请在自己的风险下使用,并为您遇到的任何错误创建合并请求。
问题陈述
我们采取了更简单的方法,而不是为 API 文档中的每个端点提供 SDK 方法,而是提供了一个通用的 ApiClient
,它可以执行对您在 GitLab API 文档 中找到的任何端点的 GET
、POST
、PUT
和 DELETE
请求。
这基于 Laravel HTTP Client 的简单性,该客户端由 Guzzle HTTP 客户端 提供,为 GitLab API 响应提供“最后代码解析”,以改善开发者体验。
此 API 客户端的价值在于它为您处理 API 请求日志、响应分页、速率限制回退和 4xx/5xx 异常处理。
有关包含预构建 Laravel Actions 的综合 SDK,包括控制台命令、服务类方法、可调度作业和 API 端点,请参阅 provisionesta/gitlab-laravel-actions 包。
示例用法
use Provisionesta\Gitlab\ApiClient;
// Get a list of records (positional arguments)
// https://docs.gitlab.com/ee/api/projects.html#list-all-projects
$projects = ApiClient::get('/projects');
// Get list of records (named arguments)
$projects = ApiClient::get(
uri: '/projects'
);
// Search for records
// https://docs.gitlab.com/ee/api/projects.html#list-all-projects
$projects = ApiClient::get(
uri: '/projects',
data: [
'search' => 'my-project-name',
'membership' => true
]
);
// Get a specific record (positional arguments)
// https://docs.gitlab.com/ee/api/projects.html#get-single-project
$project = ApiClient::get('/projects/123456789');
// Get a specific record with URL encoded path
$project = ApiClient::get('/projects/' . ApiClient::urlencode('group-name/child-group-name/project-name'));
// Create a project
// https://docs.gitlab.com/ee/api/projects.html#create-project
$group_id = '12345678';
$record = ApiClient::post(
uri: '/projects',
data: [
'name' => 'My Cool Project',
'path' => 'my-cool-project',
'namespace_id' => $group_id
]
);
// Update a project
// https://docs.gitlab.com/ee/api/projects.html#edit-project
$project_id = '123456789';
$record = ApiClient::put(
uri: '/projects/' . $project_id,
data: [
'description' => 'This is a cool project that we created for a demo.'
]
);
// Delete a project
// https://docs.gitlab.com/ee/api/projects.html#delete-project
$project_id = '123456789';
$record = ApiClient::delete(
uri: '/projects/' . $project_id
);
问题跟踪和错误报告
我们没有维护功能请求路线图,但是我们邀请您做出贡献,我们将很高兴审查您的合并请求。
请创建一个 问题 来报告错误。
贡献
请参阅 CONTRIBUTING.md 了解更多有关如何贡献的信息。
维护者
姓名 | GitLab 处理 | 电子邮件 |
---|---|---|
Jeff Martin | @jeffersonmartin | provisionesta [at] jeffersonmartin [dot] com |
贡献者致谢
安装
要求
要求 | 版本 |
---|---|
PHP | ^8.0 , ^8.1 , ^8.2 , ^8.3 |
Laravel | ^8.0 , ^9.0 , ^10.0 , ^11.0 |
升级指南
请参阅 变更日志 了解发行说明。
仍在使用 glamstack/gitlab-sdk
(v2.x) 吗?请参阅 v3.0 变更日志 了解升级说明。
仍在使用 gitlab-it/gitlab-sdk
(v3.x) 吗?请参阅 v4.0 变更日志 了解升级说明。
添加 Composer 包
composer require provisionesta/gitlab-api-client:^4.0
如果您正在为此包做出贡献,请参阅 CONTRIBUTING.md 了解如何配置带有符号链接的本地 composer 包的说明。
发布配置文件
这是可选的。配置文件指定了 API 连接存储在哪些 .env
变量名称中。只有当您想重命名 GITLAB_API_*
.env
变量名称时,才需要发布配置文件。
php artisan vendor:publish --tag=gitlab-api-client
连接凭证
环境变量
请将以下变量添加到您的 .env
文件中。您可以在文件的任何位置添加这些变量(每行一个),或者添加到文件底部(任选其一)。
GITLAB_API_URL="https://gitlab.com"
GITLAB_API_TOKEN=""
如果您将连接密钥存储在数据库或密钥管理器中,您可以覆盖 config/gitlab-api-client.php
的配置,或者在每个请求中提供连接数组。有关更多信息,请参阅 连接数组。
URL
如果您使用的是 GitLab.com SaaS(您没有托管自己的 GitLab 实例),那么 URL 是 https://gitlab.com
。如果您刚开始使用,建议在 GitLab.com 上注册一个免费账户。您可以使用 API 操作您个人命名空间中的项目,或者用于开源或组织组。
GITLAB_API_URL="https://gitlab.com"
GITLAB_API_TOKEN=""
如果您托管自己的 GitLab 自托管实例,那么 URL 是您用来登录的实例的 FQDN(例如 https://gitlab.example.com
)。
GITLAB_API_URL="https://gitlab.example.com"
GITLAB_API_TOKEN=""
如果您的 GitLab 实例位于防火墙后面,那么您需要与您的 IT 或基础设施团队合作,允许 Laravel 应用连接到 GitLab 实例。此配置取决于您的环境,且不提供支持。您可以通过使用 CURL 命令测试到 https://gitlab.example.com/api/v4/version
的初始连接性。
API 令牌
您需要在 GitLab 实例上生成一个访问令牌,并更新 .env
文件中的 GITLAB_API_TOKEN
变量。
GITLAB_API_TOKEN="glpat-S3cr3tK3yG03sH3r3"
在创建 API 令牌之前,请参阅 安全最佳实践。
连接数组
您在 .env
文件中定义的变量默认使用,除非您设置包含 URL 和 API 令牌的数组作为连接参数。
安全警告:不要将硬编码的 API 令牌提交到代码库中。这应仅用于存储在数据库或密钥管理器中的动态变量。
$connection = [
'url' => 'https://gitlab.com',
'token' => 'glpat-S3cr3tK3yG03sH3r3'
];
use Provisionesta\Gitlab\ApiClient;
class MyClass
{
private array $connection;
public function __construct($connection)
{
$this->connection = $connection;
}
public function getGroup($group_id)
{
return ApiClient::get(
connection: $this->connection,
uri: 'groups/' . $group_id
)->data;
}
}
安全最佳实践
不共享令牌
不要使用您已经为其他目的创建的 API 令牌。您应该为每个用例生成一个新的 API 令牌。
这在安全事件期间很有用,当需要在受侵害的系统上撤销密钥时,您不希望使用相同用户或服务账户的其他系统受到影响,因为这些系统使用的是未被撤销的不同密钥。
API 令牌存储
不要将您的 API 令牌添加到任何 config/*.php
文件中,以避免将其提交到您的仓库(泄露秘密)。
所有 API 令牌都应定义在 .env
文件中,该文件包含在 .gitignore
中,且不会提交到您的仓库。
对于高级用例,您可以将变量存储在 CI/CD 变量或秘密保险库(例如 Ansible Vault、AWS Parameter Store、GCP Secrets Manager、HashiCorp Vault 等)中。
API 令牌权限
在为您的应用程序创建 API 令牌之前,我们建议您阅读有关 个人访问令牌、组访问令牌、项目访问令牌 和 安全最佳实践 的更多信息。
所有 API 端点都需要 api
或 read_api
权限范围。如果您不执行任何读写操作,建议使用 read_api
权限范围作为主动安全措施。
如果您使用的是 个人访问令牌,则 API 令牌使用它所属用户的权限,因此对于生产应用程序用例,最佳实践是创建服务账户(机器人)用户。出于安全原因,大多数服务账户应该是 Regular
用户。如果您的用户账户具有 Administrator
权限,请格外小心。
GitLab 项目权限
每个API调用都对应一个或多个角色的特定权限。
除非你有需要此权限级别的特定API调用,否则不应为组访问令牌或项目访问令牌配置 所有者
或 维护者
的过度许可角色。
https://docs.gitlab.com/ee/user/permissions.html#project-members-permissions
个人访问令牌
个人访问令牌可以访问您用户帐户有权限的所有组和项目。 这是一个广泛许可的令牌,应谨慎使用。 这仅建议用于执行管理员级别API调用或需要访问多个无法使用组访问令牌执行的操作的场景。
出于安全考虑,大多数服务帐户应该是 普通用户
。 如果您的用户帐户有 管理员
访问,请务必小心。
https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html
组访问令牌
组访问令牌仅提供对特定GitLab组及其子组和GitLab项目的访问。 这是大多数场景下推荐使用的令牌类型。
https://docs.gitlab.com/ee/user/group/settings/group_access_tokens.html
项目访问令牌
项目访问令牌仅提供对其创建的项目及其关联的基于API密钥名称的bot用户的访问。 除非您仅使用此API客户端在单个项目中执行操作,否则建议使用组访问令牌或个人访问令牌。
https://docs.gitlab.com/ee/user/project/settings/project_access_tokens.html
API请求
您可以对GitLab REST API文档中的任何资源端点发起API请求。
端点 | API文档 |
---|---|
users | 列出所有用户 |
users/{id} | 通过ID获取用户 |
users/{id}/projects | 获取用户是成员的项目 |
users | 创建用户 |
groups | 所有组的列表 |
groups/{id}/descendent_groups | 列出父组的所有子组 |
groups/{id} | 通过ID获取特定组 |
projects/{id}/members | 列出所有直接组成员 |
projects/{id}/members/all | 列出所有直接和继承的组成员 |
projects/{id}/members | 将成员添加到组 |
projects/{id}/members | 从组中移除成员 |
projects | 列出所有项目 |
projects/{id} | 通过ID获取特定项目 |
projects/{id}/members | 列出所有直接项目成员 |
projects/{id}/members/all | 列出所有直接和继承的项目成员 |
projects/{id}/members | 将成员添加到项目 |
projects/{id}/members | 从项目中移除成员 |
projects/{id}/issues | 列出项目问题 |
projects/{id}/issues/{id} | 获取特定项目问题 |
projects/{id}/issues | 创建新问题 |
projects/{id}/merge_requests | 列出项目合并请求 |
projects/{id}/repository/files/{urlencoded_path} | 从存储库获取文件元数据和内容 |
依赖注入
如果您在每个类的顶部包含完全限定的命名空间,您可以在发起API调用的方法中使用类名。
use Provisionesta\Gitlab\ApiClient;
class MyClass
{
public function getGroup($group_id)
{
return ApiClient::get('groups/' . $group_id)->data;
}
}
如果您不使用依赖注入,则在使用类时需要提供完全限定的命名空间。
class MyClass
{
public function getGroup($group_id)
{
return \Provisionesta\Gitlab\ApiClient::get('groups/' . $group_id)->data;
}
}
类实例化
我们在v4.0版本中过渡到使用静态方法,您不需要实例化ApiClient类。
ApiClient::get('groups');
ApiClient::post('groups', []);
ApiClient::get('groups/12345678');
ApiClient::put('groups/12345678', []);
ApiClient::delete('groups/12345678');
命名参数与位置参数
您可以使用命名参数/参数(PHP 8中引入)或位置函数参数/参数。
如果您正在指定请求数据或使用连接数组,建议使用命名参数。如果您只是指定URI,则可以使用位置参数。
有关函数参数、命名参数以及这篇有用的博客文章,请查阅PHP文档。
// Named Arguments
ApiClient::get(
uri: 'groups'
);
// Positional Arguments
ApiClient::get('groups');
GET请求
端点以或不以/
开头,紧跟在/api/v4/
之后。GitLab API文档提供了带有前导斜杠的端点,其中已隐含了/api/v4
。是否包括端点的前导斜杠取决于您(仅为外观上的考虑)。API客户端会自动处理https://gitlab.com/api/v4/uri
的字符串拼接。
使用API客户端,您可以使用get()
方法,将端点groups
作为uri
参数。
ApiClient::get('groups');
您还可以使用变量或数据库模型来获取构建端点所需的数据。
// Get a list of records
$records = ApiClient::get('groups');
// Use variable for endpoint
$endpoint = 'groups';
$records = ApiClient::get($endpoint);
// Get a specific record
$group_id = '12345678';
$record = ApiClient::get('groups/' . $group_id);
// Get a specific record using a variable
// This assumes that you have a database column named `api_group_id` that
// contains the string with the GitLab Group ID `12345678`.
$gitlab_group = \App\Models\GitlabGroup::where('id', $id)->firstOrFail();
$record = ApiClient::get('groups/' . $gitlab_group->api_group_id);
带有查询字符串参数的GET请求
get()
方法的第二个参数是一个可选的参数数组,该数组由API客户端和Laravel HTTP客户端解析,并以查询字符串的形式呈现,其中自动添加了?
和&
。
API请求过滤
一些API端点使用search
查询字符串或其他参数来限制结果。请参阅列出用户和列出所有项目API文档中的示例。每个端点都提供了不同的选项,您可以在使用每个端点时进行审查。
// Search for records
// https://docs.gitlab.com/ee/api/projects.html#list-all-projects
$records = ApiClient::get('projects', [
'search' => 'my-project-name',
'membership' => true
]);
// This will parse the array and render the query string
// https://gitlab.com/api/v4/projects?search=my-project-name&membership=true
API响应过滤
您还可以使用Laravel Collections来过滤和转换结果,无论是使用完整的数据集还是您通过API请求已经过滤的数据。
有关更多信息,请参阅使用Laravel Collections。
POST请求
post()
方法几乎与带有参数数组的get()
请求相同,但是参数是以application/json
内容类型作为表单数据传递的,而不是作为查询字符串放在URL中。这是行业标准,并不特定于API客户端。
您可以在Laravel HTTP客户端文档中了解更多有关请求数据的信息。
// Create a project
// https://docs.gitlab.com/ee/api/projects.html#create-project
$group_id = '12345678';
$record = ApiClient::post(
uri: '/projects',
data: [
'name' => 'My Cool Project',
'path' => 'my-cool-project',
'namespace_id' => $group_id
]
);
PUT请求
put()
方法用于更新现有记录的属性。
您需要确保在第一个参数(URI)中提供了您想要更新的记录的ID。在大多数应用程序中,这将是您从数据库或其他位置获取的变量,而不会是硬编码的。
// Update a project
// https://docs.gitlab.com/ee/api/projects.html#edit-project
$project_id = '123456789';
$record = ApiClient::put(
uri: '/projects/' . $project_id,
data: [
'description' => 'This is a cool project that we created for a demo.'
]
);
DELETE请求
delete()
方法用于基于您提供的ID销毁资源的操作。
请注意,delete()
方法的返回状态码可能因供应商而异(例如,200、201、202、204等)。GitLab的API将为成功删除的资源返回204
状态码,为已安排删除的资源返回202
状态码。您应该使用$response->status->successful
布尔值来检查结果。
// Delete a project
// https://docs.gitlab.com/ee/api/projects.html#delete-project
$project_id = '123456789';
$record = ApiClient::delete('projects/' . $project_id);
类方法
以上示例展示了基本的行内使用方法,适用于大多数场景。如果您希望使用类和构造函数,下面的示例将提供一个有用的示例。
<?php
use Provisionesta\Gitlab\ApiClient;
use Provisionesta\Gitlab\Exceptions\NotFoundException;
class GitlabProjectService
{
private $connection;
public function __construct(array $connection = [])
{
// If connection is null, use the environment variables
$this->connection = !empty($connection) ? $connection : config('gitlab-api-client');
}
public function listProjects($query = [])
{
$projects = ApiClient::get(
connection: $this->connection,
uri: 'projects',
data: $query
);
return $projects->data;
}
public function getProject($id, $query = [])
{
try {
$project = ApiClient::get(
connection: $this->connection,
uri: 'projects/' . $id,
data: $query
);
} catch (NotFoundException $e) {
// Custom logic to handle a record not found. For example, you could
// redirect to a page and flash an alert message.
}
return $project->data;
}
public function storeProject($request_data)
{
$project = ApiClient::post(
connection: $this->connection,
uri: 'projects',
data: $request_data
);
// To return an object with the newly created project
return $project->data;
// To return the ID of the newly created project
// return $project->data->id;
// To return the status code of the form request
// return $project->status->code;
// To return a bool with the status of the form request
// return $project->status->successful;
// To return the entire API response with the data, headers, and status
// return $project;
}
public function updateProject($id, $request_data)
{
try {
$project = ApiClient::put(
connection: $this->connection,
uri: 'projects/' . $id,
data: $request_data
);
} catch (NotFoundException $e) {
// Custom logic to handle a record not found. For example, you could
// redirect to a page and flash an alert message.
}
// To return an object with the updated created project
return $project->data;
// To return a bool with the status of the form request
// return $project->status->successful;
}
public function deleteProject($id)
{
try {
$project = ApiClient::delete(
connection: $this->connection,
uri: 'projects/' . $id
);
} catch (NotFoundException $e) {
// Custom logic to handle a record not found. For example, you could
// redirect to a page and flash an alert message.
}
return $project->status->successful;
}
}
速率限制
在v4.0版本中,我们增加了当剩余20%速率限制时自动回退。这将通过在每个请求中实现sleep(10)
来减慢请求速度。由于速率限制每60秒重置,这将减慢接下来的5-6个请求,直到速率限制重置。
如果GitLab的速率限制超出了端点,将抛出Provisionesta\Gitlab\Exceptions\RateLimitException
。
回退会减慢请求,但如果超出速率限制,请求将失败并终止。
API响应
此API客户端使用Provisionesta标准的API响应格式。
// API Request
$group = ApiClient::get('groups/80039310');
// API Response
$group->data; // object
$group->headers; // array
$group->status; // object
$group->status->code; // int (ex. 200)
$group->status->ok; // bool (is 200 status)
$group->status->successful; // bool (is 2xx status)
$group->status->failed; // bool (is 4xx/5xx status)
$group->status->clientError; // bool (is 4xx status)
$group->status->serverError; // bool (is 5xx status)
响应数据
data
属性包含Laravel HTTP客户端object()
方法的内容,已经解析,并且是任何分页结果的最终合并输出。
$group = ApiClient::get('groups/80039310');
$group->data;
{
+"id": 80039310,
+"web_url": "https://gitlab.com/groups/provisionesta",
+"name": "provisionesta",
+"path": "provisionesta",
+"description": "Provisionesta is a library of open source packages, projects, and tools created by Jeff Martin mostly related to IAM/RBAC and REST API infrastructure and SaaS application provisioning.",
+"visibility": "public",
+"share_with_group_lock": false,
+"require_two_factor_authentication": false,
+"two_factor_grace_period": 48,
+"project_creation_level": "developer",
+"auto_devops_enabled": null,
+"subgroup_creation_level": "maintainer",
+"emails_disabled": false,
+"emails_enabled": true,
+"mentions_disabled": null,
+"lfs_enabled": true,
+"default_branch_protection": 2,
+"default_branch_protection_defaults": {},
+"avatar_url": "https://gitlab.com/uploads/-/system/group/avatar/80039310/121-automate.png",
+"request_access_enabled": true,
+"full_name": "provisionesta",
+"full_path": "provisionesta",
+"created_at": "2023-12-24T19:28:45.322Z",
+"parent_id": null,
+"organization_id": 1,
+"shared_runners_setting": "enabled",
+"ldap_cn": null,
+"ldap_access": null,
+"marked_for_deletion_on": null,
+"wiki_access_level": "enabled",
+"shared_with_groups": [],
+"runners_token": "REDACTED",
+"prevent_sharing_groups_outside_hierarchy": false,
+"projects": [],
+"shared_projects": [],
+"shared_runners_minutes_limit": 50000,
+"extra_shared_runners_minutes_limit": null,
+"prevent_forking_outside_group": false,
+"service_access_tokens_expiration_enforced": true,
+"membership_lock": false,
+"ip_restriction_ranges": null,
+"unique_project_download_limit": 0,
+"unique_project_download_limit_interval_in_seconds": 0,
+"unique_project_download_limit_allowlist": [],
+"unique_project_download_limit_alertlist": [
4572001,
],
+"auto_ban_user_on_excessive_projects_download": false,
}
访问单个记录值
您可以使用对象符号访问这些变量。这是处理API响应最常见的情况。
$group = ApiClient::get('groups/80039310')->data;
$group_name = $group->path;
// provisionesta
遍历记录
如果您有一个包含多个对象数组的记录,您可以遍历这些记录。API客户端会自动分页和合并记录数组,以改善开发者体验。
$groups = ApiClient::get('groups')->data;
foreach($groups as $group) {
dd($group->path);
// provisionesta
}
缓存响应
API客户端不使用缓存,以避免您在控制要缓存的端点时遇到任何限制。
当进行API调用时,您可以在端点周围包装一个缓存外观。您可以在Laravel Cache文档中了解更多信息。
use Illuminate\Support\Facades\Cache;
use Provisionesta\Gitlab\ApiClient;
$groups = Cache::remember('gitlab_groups', now()->addHours(12), function () {
return ApiClient::get('groups')->data;
});
foreach($groups as $group) {
dd($group->path);
// provisionesta
}
在获取特定ID或传递额外参数时,请确保传递变量到use($var1, $var2)
中。
$group_id = '12345678';
$groups = Cache::remember('gitlab_group_' . $group_id, now()->addHours(12), function () use ($group_id) {
return ApiClient::get('groups/' . $group_id)->data;
});
日期格式化
您可以使用Carbon库来格式化日期和执行计算。
$created_date = Carbon::parse($group->data->created_at)->format('Y-m-d');
// 2023-01-01
$created_age_days = Carbon::parse($group->data->created_at)->diffInDays();
// 265
使用Laravel集合
您可以使用Laravel Collections,它是一组强大的数组辅助工具,类似于您可能已经熟悉的数组搜索和SQL查询。
有关更多信息,请参阅使用Laravel集合解析响应文档。
响应头
由于键使用连字符,会与访问键和值的语法冲突,因此返回数组而不是对象。
$group = ApiClient::get('groups/12345678');
$group->headers;
[
+"Date": "Thu, 06 Jan 2022 21:40:18 GMT",
+"Content-Type": "application/json",
+"Transfer-Encoding": "chunked",
+"Connection": "keep-alive",
+"Cache-Control": "max-age=0, private, must-revalidate",
+"Etag": "W/"ed65096017d349b25371385b9b96d102"",
+"Vary": "Origin",
+"X-Content-Type-Options": "nosniff",
+"X-Frame-Options": "SAMEORIGIN",
+"X-Request-Id": "01FRRNBQCPG9XKBY8211NEH285",
+"X-Runtime": "0.100822",
+"Strict-Transport-Security": "max-age=31536000",
+"Referrer-Policy": "strict-origin-when-cross-origin",
+"RateLimit-Observed": "6",
+"RateLimit-Remaining": "1994",
+"RateLimit-Reset": "1641505278",
+"RateLimit-ResetTime": "Thu, 06 Jan 2022 21:41:18 GMT",
+"RateLimit-Limit": "2000",
+"GitLab-LB": "fe-12-lb-gprd",
+"GitLab-SV": "localhost",
+"CF-Cache-Status": "DYNAMIC",
+"Expect-CT": "max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"",
+"Report-To": "{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=LWJRP1mJdxCzclW3zKzqg40CbYJeUJ2mf2aRLBRfzxWvAgh15LrCQwpmqtk%2B4cJoDWsX3bx1yAkEB9HuokEMgKg%2FMkFXLoy2N8oE09KfHIH%2B8YWjBmX%2BdUD4hkg%3D"}],"group":"cf-nel","max_age":604800}",
+"NEL": "{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}",
+"Server": "cloudflare",
+"CF-RAY": "6c981a9ba95639a7-SEA",
]
获取头值
$content_type = $group->headers['Content-Type'];
// application/json
响应状态
有关不同状态布尔值的更多信息,请参阅Laravel HTTP客户端文档。
$group = ApiClient::get('groups/12345678');
$group->status;
{
+"code": 200 // int (ex. 200)
+"ok": true // bool (is 200 status)
+"successful": true // bool (is 2xx status)
+"failed": false // bool (is 4xx/5xx status)
+"serverError": false // bool (is 4xx status)
+"clientError": false // bool (is 5xx status)
}
API响应状态码
$group = ApiClient::get('groups/12345678');
$status_code = $group->status->code;
// 200
错误响应
对于任何4xx或5xx响应都会抛出异常。所有响应都会自动记录。
异常
代码 | 异常类 |
---|---|
N/A | Provisionesta\Gitlab\Exceptions\ConfigurationException |
400 | Provisionesta\Gitlab\Exceptions\BadRequestException |
401 | Provisionesta\Gitlab\Exceptions\UnauthorizedException |
403 | Provisionesta\Gitlab\Exceptions\ForbiddenException |
404 | Provisionesta\Gitlab\Exceptions\NotFoundException |
409 | Provisionesta\Gitlab\Exceptions\ConflictException |
412 | Provisionesta\Gitlab\Exceptions\PreconditionFailedException |
422 | Provisionesta\Gitlab\Exceptions\UnprocessableException |
429 | Provisionesta\Gitlab\Exceptions\RateLimitException |
500 | Provisionesta\Gitlab\Exceptions\ServerErrorException |
503 | Provisionesta\Gitlab\Exceptions\ServiceUnavailableException |
捕获异常
您可以捕获您想要静默处理的任何异常。任何未捕获的异常都会对用户可见,并导致在您的监控软件中出现的500错误。
use Provisionesta\Gitlab\Exceptions\NotFoundException;
try {
$group = ApiClient::get('groups/12345678');
} catch (NotFoundException $e) {
// Group is not found. You can create a log entry, throw an exception, or handle it another way.
Log::error('GitLab group could not be found', ['gitlab_group_id' => $group_id]);
}
禁用异常
如果您不希望抛出异常,您可以为GitLab API客户端全局禁用异常,并自行处理每个请求的状态。只需在您的.env
文件中设置GITLAB_API_EXCEPTIONS=false
即可。
GITLAB_API_EXCEPTIONS=false
使用 Laravel 集合解析响应
您可以使用Laravel Collections,它是一组强大的数组辅助工具,类似于您可能已经熟悉的数组搜索和SQL查询。
$project_id = '12345678';
$issues = ApiClient::get('projects/' . $project_id . '/issues');
$issue_collection = collect($issues->data)->where('state', 'closed')->toArray();
// This will return an array of all issues that have been closed
为了语法规范和可读性,您可以轻松地将这些内容合并为一行。由于 ApiClient 自动处理任何 4xx 或 5xx 错误,因此您无需担心 try/catch 异常。
$users = collect(ApiClient::get('projects/' . $project_id . '/issues')->data)
->where('state', 'closed')
->toArray();
此方法可以使您获得与执行 SQL 查询相同的优势,并且在使用集合时感觉非常熟悉。
SELECT * FROM issues WHERE project_id='12345678' AND state='closed';
集合方法
以下是最常用的、用于过滤数据的常用方法
Laravel 文档 | 使用示例 |
---|---|
count | 使用示例 |
countBy | 使用示例 |
except | N/A |
filter | N/A |
flip | N/A |
groupBy | 使用示例 |
keyBy | N/A |
only | N/A |
pluck | 使用示例 |
sort | 使用示例 |
sortBy | 使用示例 |
sortKeys | 使用示例 |
toArray | N/A |
transform | 使用示例 |
unique | 使用示例 |
values | 使用示例 |
where | N/A |
whereIn | N/A |
whereNotIn | N/A |
集合简化数组
Pluck 方法
您可以使用集合的 pluck 方法来获取特定的属性。
// Get an array of issue titles
$issue_titles = collect(ApiClient::get('projects/' . $project_id . '/issues')->data)
->where('state', 'closed')
->pluck('title')
->toArray();
// [
// 0 => 'Lorem ipsum dolor sit amet',
// 1 => 'Donec malesuada leo et efficitur imperdiet',
// 2 => 'Aliquam dignissim tortor faucibus',
// 3 => 'Sed convallis velit id massa',
// 4 => 'Vivamus congue quam eget nisl pharetra',
// 5 => 'Suspendisse finibus odio vitae',
// ]
您还可以使用 pluck 方法来获取两个属性,并将其中一个设置为数组键,另一个设置为数组值。
// Get an array with title keys and author array values
$issue_titles_authors = collect(ApiClient::get('projects/' . $project_id . '/issues')->data)
->where('state', 'closed')
->pluck('author', 'title')
->toArray();
// [
// 'Lorem ipsum dolor sit amet' => {
// +"id": 123456,
// +"username": "z3r0c00l.example",
// +"name": "Dade Murphy",
// +"state": "active",
// +"locked": false,
// +"web_url": "https://gitlab.com/z3r0c00l.example",
// },
// 'Donec malesuada leo et efficitur imperdiet' => {
// // truncated for docs
// },
// 'Aliquam dignissim tortor faucibus' => {
// // truncated for docs
// },
// 'Sed convallis velit id massa' => {
// // truncated for docs
// },
// 'Vivamus congue quam eget nisl pharetra' => {
// // truncated for docs
// },
// 'Suspendisse finibus odio vitae' => {
// // truncated for docs
// },
// ]
使用点符号表示嵌套数组属性
如果只想返回一个字符串,则在使用 pluck 方法时可以使用点符号。您还可以在包括 where 和 groupBy 方法在内的大多数其他集合方法中使用点符号。
$issue_titles_authors = collect(ApiClient::get('projects/' . $project_id . '/issues')->data)
->where('state', 'closed')
->pluck('author.name', 'title')
->toArray();
// [
// 'Lorem ipsum dolor sit amet' => "Dade Murphy",
// 'Donec malesuada leo et efficitur imperdiet' => "Kate Libby",
// 'Aliquam dignissim tortor faucibus' => "Kate Libby",
// 'Sed convallis velit id massa' => "Dade Murphy",
// 'Vivamus congue quam eget nisl pharetra' => "Paul Cook",
// 'Suspendisse finibus odio vitae' => "Joey Pardella",
// ]
变换数组
当处理 API 返回的记录时,您将有许多当前用例中不需要的数据。
您可以使用 transform 方法对每个记录执行 foreach 循环,并创建一个新的数组,其中包含您想要的特定字段。
您可以将 $item 变量视为具有特定记录所有元数据的 foreach($users as $item) { }
。
transform 方法使用函数(即闭包)返回应成为此特定数组键的新值的数组。
$issue_titles_authors = collect(ApiClient::get('projects/' . $project_id . '/issues')->data)
->where('state', 'closed')
->pluck('author', 'title')
->transform(function($item) {
return [
'id' => $item->id,
'name' => $item->name
];
})->toArray();
// [
// 'Lorem ipsum dolor sit amet' => {
// +"id": 123456,
// +"name": "Dade Murphy",
// },
// 'Donec malesuada leo et efficitur imperdiet' => {
// // truncated for docs
// },
// 'Aliquam dignissim tortor faucibus' => {
// // truncated for docs
// },
// 'Sed convallis velit id massa' => {
// // truncated for docs
// },
// 'Vivamus congue quam eget nisl pharetra' => {
// // truncated for docs
// },
// 'Suspendisse finibus odio vitae' => {
// // truncated for docs
// },
// ]
箭头函数
如果您的所有转换都可以在数组内直接完成,而不需要定义额外的变量,则可以使用简写箭头函数。这仅是个人偏好,并非要求。
$users = collect(ApiClient::get('projects/' . $project_id . '/issues')->data)
->where('state', 'closed')
->pluck('author', 'title')
->transform(fn($item) => [
'id' => $item->id,
'name' => $item->name
])->toArray();
计算值
如果您想返回一个计算得到的数组或字符串,可以在 transform 方法中执行它们,就像使用具有输入和输出的正常函数一样。
$issue_titles_authors = collect(ApiClient::get('projects/' . $project_id . '/issues')->data)
->where('state', 'closed')
->pluck('author', 'title')
->transform(function($item) {
// Disclaimer: Performing individual API calls on large data sets
// may exhaust rate limits and is computationally intensive.
$user = ApiClient::get('users/' . $item->id)->data;
return $user->email;
})->toArray();
// [
// 'Lorem ipsum dolor sit amet' => "dmurphy@example.com",
// 'Donec malesuada leo et efficitur imperdiet' => "klibby@example.com",
// 'Aliquam dignissim tortor faucibus' => "klibby@example.com",
// 'Sed convallis velit id massa' => "dmurphy@example.com",
// 'Vivamus congue quam eget nisl pharetra' => "pcook@example.com",
// 'Suspendisse finibus odio vitae' => "jpardella@example.com",
// ]
Unique 方法
您可以使用 unique 方法获取独特属性值列表(例如作者姓名)。
$unique_authors = collect(ApiClient::get('projects/' . $project_id . '/issues')->data)
->where('state', 'closed')
->unique('author.name')
->pluck('author.name')
->toArray();
// [
// 36 => 'Dade Murphy',
// 111 => 'Kate Libby',
// 238 => 'Paul Cook',
// 288 => 'Joey Pardella'
// ]
Values 方法
当使用 unique 方法时,它使用找到的第一个记录的键。您应该在最后添加 values 方法,以根据结果数量重置所有键整数。
$unique_authors = collect(ApiClient::get('projects/' . $project_id . '/issues')->data)
->where('state', 'closed')
->unique('author.name')
->pluck('author.name')
->values()
->toArray();
// [
// 0 => 'Dade Murphy',
// 1 => 'Kate Libby',
// 2 => 'Paul Cook',
// 3 => 'Joey Pardella'
// ]
排序方法
您可以通过属性值进行字母排序。只需将属性提供给 sortBy 方法即可(支持嵌套数组值)。如果您已经使用了 pluck 方法,并且数组值是字符串,则可以使用不接收参数的 sort 方法。
// Option 1
$unique_job_titles = collect(ApiClient::get('projects/' . $project_id . '/issues')->data)
->where('state', 'closed')
->sortBy('author.name')
->unique('author.name')
->pluck('author.name')
->values()
->toArray();
// Option 2
$unique_job_titles = collect(ApiClient::get('projects/' . $project_id . '/issues')->data)
->where('state', 'closed')
->unique('author.name')
->pluck('author.name')
->sort()
->values()
->toArray();
// [
// 0 => 'Dade Murphy',
// 1 => 'Joey Pardella'
// 2 => 'Kate Libby',
// 3 => 'Paul Cook',
// ]
如果您有数组键字符串,可以使用 sortKeys 方法按字母顺序对结果数组的键进行排序。
计数方法
您可以使用 count 方法获取所有方法应用后结果的总量。这作为对 toArray 的替代,这样您就得到一个整数值,而不是需要执行 count($collection_array)
。
// Get a count of issues
$unique_job_titles = collect(ApiClient::get('projects/' . $project_id . '/issues')->data)
->where('state', 'closed')
->count();
// 376
您可以使用 countBy 方法获取唯一属性值的计数。您应该使用 sortKeys 方法按字母顺序对结果数组的键进行排序。
// Get a count of unique job titles
$unique_job_titles = collect(ApiClient::get('projects/' . $project_id . '/issues')->data)
->where('state', 'closed')
->countBy('author.name')
->sortKeys()
->toArray();
// [
// 'Dade Murphy' => 2,
// 'Joey Pardella' => 1,
// 'Kate Libby' => 2,
// 'Paul Cook' => 1
// ]
分组方法
尽管您可以使用与原始响应一起的 groupBy 方法,但在数据分组后很难操作数据,因此建议在将数据转换后,将 groupBy('attribute_name')
添加到您的集合链的末尾。请注意,在转换数据时,您已重命名了数组值键(属性),因此您想使用新的数组键。
$users = collect(ApiClient::get('projects/' . $project_id . '/issues')->data)
->where('state', 'closed')
->transform(fn($item) => [
'id' => $item->id,
'title' => $item->title,
'author_name' => $item->author->name,
])->sortBy('author_name')
->groupBy('author_name')
->toArray();
// "Dade Murphy" => [
// [
// "id" => "36",
// "title" => "Lorem ipsum dolor sit amet",
// "author_name" => "Dade Murphy",
// ],
// [
// "id" => "368",
// "title" => "Sed convallis velit id massa",
// "author_name" => "Dade Murphy",
// ]
// ],
// "Joey Pardella" => [
// [
// "id" => "288",
// "title" => "Suspendisse finibus odio vitae",
// "author_name" => "Joey Pardella",
// ],
// ],
// "Kate Libby" => [
// [
// "id" => "111",
// "title" => "Donec malesuada leo et efficitur imperdiet",
// "author_name" => "Kate Libby",
// ],
// [
// "id" => "219",
// "title" => "Aliquam dignissim tortor faucibus",
// "author_name" => "Kate Libby",
// ]
// ],
// "Paul Cook" => [
// [
// "id" => "238",
// "title" => "Vivamus congue quam eget nisl pharetra",
// "author_name" => "Paul Cook",
// ],
// ]
阅读更多内容
有关其他用法,请参阅 Laravel Collections 文档。有关更多实际示例,请参阅 provisionesta/gitlab-laravel-actions 包。
日志示例
此包使用 provisionesta/audit 包进行标准日志记录。
请求数据日志配置
为了提高日志的有用性,将随 POST 和 PUT 请求一起发送的 data
键/值对进行了记录。您可以选择在 .env
文件中禁用(排除)特定方法的 request_data
。
GITLAB_API_LOG_REQUEST_DATA_GET_ENABLED=true
GITLAB_API_LOG_REQUEST_DATA_POST_ENABLED=false # default is true
GITLAB_API_LOG_REQUEST_DATA_PUT_ENABLED=false # default is true
GITLAB_API_LOG_REQUEST_DATA_DELETE_ENABLED=true
如果您想排除特定的键,它们可以在您 发布配置文件 后在 config/gitlab-api-client.php
文件中设置。
默认情况下,key
和 password
字段从 GET
请求中排除,而 content
从 POST
和 PUT
请求中排除(例如,存储库文件的 base64 编码内容)。
事件类型
应使用 event_type
键进行任何分类和日志搜索。
- 格式:
gitlab.api.{method}.{result/log_level}.{reason?}
- 方法:
get|post|patch|put|delete
状态码 | 事件类型 | 日志级别 |
---|---|---|
N/A | gitlab.api.test.success | DEBUG |
N/A | gitlab.api.test.error | CRITICAL |
N/A | gitlab.api.validate.error | CRITICAL |
N/A | gitlab.api.get.process.pagination.started | DEBUG |
N/A | gitlab.api.get.process.pagination.finished | DEBUG |
N/A | gitlab.api.rate-limit.approaching | CRITICAL |
N/A | gitlab.api.rate-limit.exceeded (预异常) | CRITICAL |
N/A | gitlab.api.{method}.error.http.exception | ERROR |
200 | gitlab.api.{method}.success | DEBUG |
201 | gitlab.api.{method}.success | DEBUG |
202 | gitlab.api.{method}.success | DEBUG |
204 | gitlab.api.{method}.success | DEBUG |
400 | gitlab.api.{method}.warning.bad-request | WARNING |
401 | gitlab.api.{method}.error.unauthorized | ERROR |
403 | gitlab.api.{method}.error.forbidden | ERROR |
404 | gitlab.api.{method}.warning.not-found | WARNING |
405 | gitlab.api.{method}.error.method-not-allowed | ERROR |
412 | gitlab.api.{method}.error.precondition-failed | DEBUG |
422 | gitlab.api.{method}.error.unprocessable | DEBUG |
429 | gitlab.api.{method}.critical.rate-limit | CRITICAL |
500 | gitlab.api.{method}.critical.server-error | CRITICAL |
501 | gitlab.api.{method}.error.not-implemented | ERROR |
503 | gitlab.api.{method}.critical.server-unavailable | CRITICAL |
测试连接
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::get Success {"event_type":"gitlab.api.get.success","method":"Provisionesta\\Gitlab\\ApiClient::get","event_ms":493,"metadata":{"url":"https://gitlab.example.com/api/v4/version"}}
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::testConnection Success {"event_type":"gitlab.api.test.success","method":"Provisionesta\\Gitlab\\ApiClient::testConnection"}
成功请求
GET 请求日志
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::get Success {"event_type":"gitlab.api.get.success","method":"Provisionesta\\Gitlab\\ApiClient::get","event_ms":885,"metadata":{"url":"https://gitlab.example.com/api/v4/groups/25"}}
GET 分页请求日志
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::get Success {"event_type":"gitlab.api.get.success","method":"Provisionesta\\Gitlab\\ApiClient::get","count_records":100,"event_ms":986,"event_ms_per_record":9,"metadata":{"rate_limit_remaining":null,"url":"https://gitlab.example.com/api/v4/groups"}}
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::get Paginated Results Process Started {"event_type":"gitlab.api.get.process.pagination.started","method":"Provisionesta\\Gitlab\\ApiClient::get","metadata":{"uri":"groups"}}
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::getPaginatedResults Success {"event_type":"gitlab.api.getPaginatedResults.success","method":"Provisionesta\\Gitlab\\ApiClient::getPaginatedResults","count_records":100,"event_ms":904,"event_ms_per_record":9,"metadata":{"rate_limit_remaining":null,"url":"https://gitlab.example.com/api/v4/groups?order_by=name&owned=false&page=2&per_page=100&sort=asc&statistics=false&with_custom_attributes=false"}}
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::getPaginatedResults Success {"event_type":"gitlab.api.getPaginatedResults.success","method":"Provisionesta\\Gitlab\\ApiClient::getPaginatedResults","count_records":20,"event_ms":391,"event_ms_per_record":19,"metadata":{"rate_limit_remaining":null,"url":"https://gitlab.example.com/api/v4/groups?order_by=name&owned=false&page=3&per_page=100&sort=asc&statistics=false&with_custom_attributes=false"}}
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::get Paginated Results Process Complete {"event_type":"gitlab.api.get.process.pagination.finished","method":"Provisionesta\\Gitlab\\ApiClient::get","duration_ms":2287,"metadata":{"uri":"groups"}}
带有 URL 编码路径的 GET 请求
cool-group/my-cool-project
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::get Success {"event_type":"gitlab.api.get.success","method":"Provisionesta\\Gitlab\\ApiClient::get","event_ms":1160,"metadata":{"url":"https://gitlab.example.com/api/v4/projects/cool%2Dgroup%2Fmy%2Dcool%2Dproject"}}
POST 请求日志
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::post Success {"event_type":"gitlab.api.post.success","method":"Provisionesta\\Gitlab\\ApiClient::post","event_ms":1552,"metadata":{"url":"https://gitlab.example.com/api/v4/projects","request_data":{"name":"My Cool Project3","path":"my-cool-project3","namespace_id":"123"}}}
PUT 成功日志
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::put Success {"event_type":"gitlab.api.put.success","method":"Provisionesta\\Gitlab\\ApiClient::put","event_ms":423,"metadata":{"url":"https://gitlab.example.com/api/v4/projects/12345","request_data":{"description":"cool project description2"}}}
DELETE 成功日志
计划删除将返回 202 状态码而不是 204 状态码。
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::delete Success {"event_type":"gitlab.api.delete.success","method":"Provisionesta\\Gitlab\\ApiClient::delete","errors":{"message":"202 Accepted"},"event_ms":373,"metadata":{"url":"https://gitlab.example.com/api/v4/projects/12345"}}
错误
401 未授权
环境变量未设置
[YYYY-MM-DD HH:II:SS] local.CRITICAL: ApiClient::validateConnection Error {"event_type":"gitlab.api.validate.error","method":"Provisionesta\\Gitlab\\ApiClient::validateConnection","errors":["The url field is required.","The token field is required."]}
Provisionesta\Gitlab\Exceptions\ConfigurationException
Gitlab API configuration validation error. This occurred in Provisionesta\Gitlab\ApiClient::validateConnection. (Solution) The url field is required. The token field is required.
无效令牌
[YYYY-MM-DD HH:II:SS] local.ERROR: ApiClient::get Client Error {"event_type":"gitlab.api.get.error.unauthorized","method":"Provisionesta\\Gitlab\\ApiClient::get","errors":{"message":"401 Unauthorized"},"event_ms":225,"metadata":{"url":"https://gitlab.com/api/v4/projects/12345678","rate_limit_remaining":"1999"}}
Provisionesta\Gitlab\Exceptions\UnauthorizedException
The `GITLAB_API_TOKEN` has been configured but is invalid. (Reason) This usually happens if it does not exist, expired, or does not have permissions. (Solution) Please generate a new API Token and update the variable in your `.env` file.
404 未找到
[YYYY-MM-DD HH:II:SS] local.WARNING: ApiClient::get Client Error {"event_type":"gitlab.api.get.warning.not-found","method":"Provisionesta\\Gitlab\\ApiClient::get","errors":{"message":"404 Project Not Found"},"event_ms":253,"metadata":{"url":"https://gitlab.com/api/v4/projects/12345678"}}