gitlab-it / gitlab-sdk
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 客户端的简洁性,该客户端由 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令牌将使用其所属用户的权限,因此在生产应用场景中,建议创建一个服务账户(机器人)用户。出于安全考虑,大多数服务账户应设置为普通用户。如果您的用户账户具有管理员权限,请务必谨慎。
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组和任何子组的访问权限。这是大多数用例推荐使用的令牌类型。
https://docs.gitlab.com/ee/user/group/settings/group_access_tokens.html
项目访问令牌
项目访问令牌仅提供对其创建的特定GitLab项目的访问权限,并根据您创建的API密钥名称关联到机器人用户。除非您仅使用此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客户端,您可以通过将端点groups
作为uri
参数,使用get()
方法。
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()
方法用于更新现有记录的属性。
您需要确保要更新的记录的ID在第一个参数(URI)中提供。在大多数应用程序中,这将是您从数据库或其他位置获取的变量,而不是硬编码的。
// 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;
}
}
速率限制
在4.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响应,都会抛出异常。所有响应都会自动记录。
异常
代码 | 异常类 |
---|---|
不适用 | 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 | 不适用 |
filter | 不适用 |
flip | 不适用 |
groupBy | 使用示例 |
keyBy | 不适用 |
only | 不适用 |
pluck | 使用示例 |
sort | 使用示例 |
sortBy | 使用示例 |
sortKeys | 使用示例 |
toArray | 不适用 |
transform | 使用示例 |
unique | 使用示例 |
values | 使用示例 |
where | 不适用 |
whereIn | 不适用 |
whereNotIn | 不适用 |
集合简化数组
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
状态代码 | 事件类型 | 日志级别 |
---|---|---|
不适用 | gitlab.api.test.success | DEBUG |
不适用 | gitlab.api.test.error | CRITICAL |
不适用 | gitlab.api.validate.error | CRITICAL |
不适用 | gitlab.api.get.process.pagination.started | DEBUG |
不适用 | gitlab.api.get.process.pagination.finished | DEBUG |
不适用 | gitlab.api.rate-limit.approaching | CRITICAL |
不适用 | gitlab.api.rate-limit.exceeded (Pre-Exception) | CRITICAL |
不适用 | 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"}}