glamstack / gitlab-sdk
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.
- dev-main
- 4.1
- 4.0
- 4.0-rc1
- 3.0
- 2.1.14
- dev-release/4.0-rc1
- dev-release/3.0
- dev-update-readme-with-v3-changes
- dev-feature/refactor-logs-and-exception-handling
- dev-feature/refactor-apiclient
- dev-fix-service-provider-methods
- dev-feature/add-response-log-trait
- dev-feature/update-contributing-guide
- dev-feature/add-gitlab-sdk-config-file
- dev-feature/add-exception-classes
- dev-dev
- dev-11-upgrade-sdk-to-use-laravel-9
- dev-12-update-sdk-to-use-connection_key-and-connection_config-for-initiailization
This package is auto-updated.
Last update: 2024-03-22 13:20:49 UTC
README
[[目录]]
概述
GitLab API 客户端是一个开源的 Composer 包,用于在 Laravel 应用程序中连接到 GitLab SaaS 或自管理的实例,以提供和取消提供用户、组、项目和其他相关功能。
此项目由开源社区维护,不由任何公司维护。请在自己的风险下使用,并为您遇到的任何错误创建合并请求。
问题声明
我们不是为 API 文档中的每个端点提供 SDK 方法,而是采取了更简单的方法,提供了一个通用的 ApiClient
,该客户端可以执行 GET
、POST
、PUT
和 DELETE
请求到您在 GitLab API 文档 中找到的任何端点。
这是基于 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 |
升级指南
请参阅 changelog 了解发布说明。
仍在使用 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令牌之前,请参阅安全最佳实践。
连接数组
除非您设置了包含URL和API令牌的数组作为连接参数,否则默认使用您在.env
文件中定义的变量。
安全警告:不要将硬编码的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参数存储、GCP秘密管理器、HashiCorp保险库等)中。
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组及其任何子组和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客户端,您使用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()
方法用于更新现有记录的属性。
您需要确保要更新的记录的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;
}
}
速率限制
在 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 缓存文档中了解更多信息。
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 集合,这些是强大的数组辅助工具,类似于您可能已经熟悉的数组搜索和 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 集合,这些是强大的数组辅助工具,类似于您可能已经熟悉的数组搜索和 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请求一起发送的键值对。您可以在.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 (预异常) | 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"}}
GET 带URL编码路径的请求
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"}}