glamstack/gitlab-sdk

此包已被弃用且不再维护。作者建议使用 gitlab-it/gitlab-sdk 包。

GitLab API 客户端用于 Laravel

4.1 2024-03-22 13:15 UTC

README

[[目录]]

概述

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

此项目由开源社区维护,不由任何公司维护。请在自己的风险下使用,并为您遇到的任何错误创建合并请求。

问题声明

我们不是为 API 文档中的每个端点提供 SDK 方法,而是采取了更简单的方法,提供了一个通用的 ApiClient,该客户端可以执行 GETPOSTPUTDELETE 请求到您在 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@jeffersonmartinprovisionesta [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端点都需要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组及其任何子组和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
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 集合,这些是强大的数组辅助工具,类似于您可能已经熟悉的数组搜索和 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请求一起发送的键值对。您可以在.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(预异常)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"}}

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