gitlab-it/gitlab-sdk

Laravel 的 GitLab API 客户端

4.1 2024-03-22 13:15 UTC

This package is auto-updated.

Last update: 2024-09-22 05:57:01 UTC


README

[[目录]]

概述

GitLab API 客户端是一个开源的 Composer 包,用于 Laravel 应用程序中连接到 GitLab SaaS 或自托管实例,以提供用户、组、项目和其他相关功能的配置和取消配置。

该包由开源社区维护,不由任何公司维护。请自行承担风险,并为您遇到的任何错误创建合并请求。

问题陈述

我们不是为 API 文档中的每个端点提供 SDK 方法,而是通过提供一个通用的 ApiClient 来简化了操作,该客户端可以对您在 GitLab API 文档 中找到的任何端点执行 GETPOSTPUTDELETE 请求。

这基于 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@jeffersonmartinprovisionesta [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端点都需要apiread_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
400Provisionesta\Gitlab\Exceptions\BadRequestException
401Provisionesta\Gitlab\Exceptions\UnauthorizedException
403Provisionesta\Gitlab\Exceptions\ForbiddenException
404Provisionesta\Gitlab\Exceptions\NotFoundException
409Provisionesta\Gitlab\Exceptions\ConflictException
412Provisionesta\Gitlab\Exceptions\PreconditionFailedException
422Provisionesta\Gitlab\Exceptions\UnprocessableException
429Provisionesta\Gitlab\Exceptions\RateLimitException
500Provisionesta\Gitlab\Exceptions\ServerErrorException
503Provisionesta\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 方法。您还可以使用点表示法与大多数其他集合方法一起使用,包括 wheregroupBy 方法。

$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文件中设置它们。

默认情况下,keypassword字段从GET请求中排除,而contentPOSTPUT请求中排除(例如,存储库文件的base64编码内容)。

事件类型

应使用event_type键进行任何分类和日志搜索。

  • 格式: gitlab.api.{method}.{result/log_level}.{reason?}
  • 方法: get|post|patch|put|delete
状态代码事件类型日志级别
不适用gitlab.api.test.successDEBUG
不适用gitlab.api.test.errorCRITICAL
不适用gitlab.api.validate.errorCRITICAL
不适用gitlab.api.get.process.pagination.startedDEBUG
不适用gitlab.api.get.process.pagination.finishedDEBUG
不适用gitlab.api.rate-limit.approachingCRITICAL
不适用gitlab.api.rate-limit.exceeded (Pre-Exception)CRITICAL
不适用gitlab.api.{method}.error.http.exceptionERROR
200gitlab.api.{method}.successDEBUG
201gitlab.api.{method}.successDEBUG
202gitlab.api.{method}.successDEBUG
204gitlab.api.{method}.successDEBUG
400gitlab.api.{method}.warning.bad-requestWARNING
401gitlab.api.{method}.error.unauthorizedERROR
403gitlab.api.{method}.error.forbiddenERROR
404gitlab.api.{method}.warning.not-foundWARNING
405gitlab.api.{method}.error.method-not-allowedERROR
412gitlab.api.{method}.error.precondition-failedDEBUG
422gitlab.api.{method}.error.unprocessableDEBUG
429gitlab.api.{method}.critical.rate-limitCRITICAL
500gitlab.api.{method}.critical.server-errorCRITICAL
501gitlab.api.{method}.error.not-implementedERROR
503gitlab.api.{method}.critical.server-unavailableCRITICAL

测试连接

[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"}}