spinen/halo-php-client

SPINEN 的 Halo PHP 客户端。

1.0.1 2024-04-14 21:41 UTC

README

Latest Stable Version Latest Unstable Version Total Downloads License

PHP 包,用于与 Halo 服务解决方案 接口。我们强烈建议您查看 Halo 的 API 文档,以了解此包的功能,因为我们只是封装了他们的 API。

我们仅使用 Laravel 作为我们的应用程序,因此此包是以 Laravel 为出发点编写的。我们已经尝试使其在 Laravel 之外也能工作。如果社区有请求将此包分成两部分,我们将考虑进行这项工作。

构建状态

目录

安装

通过 Composer 安装 Halo PHP 包

$ composer require spinen/halo-php-client

Laravel 设置

  1. 您需要让您的 User 对象实现包含 Spinen\Halo\Concerns\HasHalo 特性的 trait,这将允许它像这样访问客户端:$user->halo

    <?php
    
    namespace App;
    
    use Illuminate\Contracts\Auth\MustVerifyEmail;
    use Illuminate\Foundation\Auth\User as Authenticatable;
    use Illuminate\Notifications\Notifiable;
    use Spinen\Halo\Concerns\HasHalo;
    
    class User extends Authenticatable
    {
        use HasHalo, Notifiable;
    
        // ...
    }
  2. 将适当的值添加到您的 .env 文件中

    密钥

    HALO_RESOURCE_SERVER=<API Resource Server>
    HALO_TENANT=<Optional Tenant>
    HALO_AUTHORIZATION_CODE_CLIENT_ID=<Needed if using Authorization Code>
    HALO_CLIENT_CREDENTIALS_CLIENT_ID=<Needed if using Client Credentials>
    HALO_CLIENT_CREDENTIALS_CLIENT_SECRET=<Needed if using Client Credentials>
  3. [可选] 发布配置和迁移

    配置

    可以运行以下命令将名为 halo.php 的配置文件发布到 config/ 目录...

    php artisan vendor:publish --tag=halo-config

    迁移

    可以运行以下命令将迁移文件发布...

    php artisan vendor:publish --tag=halo-migrations

    您需要迁移来在您的 User 模型上设置 Halo API 令牌。

通用 PHP 设置

  1. 您需要构建一个数组作为配置传递给 Halo 对象。您可以在 configs 目录中查看 halo.php 文件。文件中记录了所有属性。

  2. 根据您的需求,您可以使用 Halo 客户端或 Builder。

    要获取客户端凭证的 Spinen\Halo\Api\Client 实例...

    $ psysh
    Psy Shell v0.11.14 (PHP 8.2.4 — cli) by Justin Hileman
    > $configs = [
        "oauth" => [
            "authorization_server" => "https://some.host.tld/auth",
            "client_credentials" => [
                "id" => "client_id",
                "secret" => "client_secret",
            ],
        ],
        "resource_server" => "https://some.host.tld/api",
    ]
    
    > $halo = new Spinen\Halo\Api\Client(configs: $configs);
    = Spinen\Halo\Api\Client {#2744}

    $halo 实例将像以下所有示例中的 halo 属性一样工作。

    要获取 Spinen\Halo\Support\Builder 实例...

    $ psysh
    Psy Shell v0.11.12 (PHP 8.2.4 — cli) by Justin Hileman
    New version is available at psysh.org/psysh (current: v0.11.12, latest: v0.11.13)
    > // Get a $halo instance from above
    
    > $builder = (new Spinen\Halo\Support\Builder)->setClient($halo);
    = Spinen\Halo\Support\Builder {#2757}
    
    >

    $builder 实例将像以下所有示例中的 halo() 方法一样工作。

身份验证

Halo 在进行 API 调用时具有四种身份验证方式。在此包中,我们专注于以下两种:1) 授权代码 或 2) 客户端凭证。使用授权代码方法可以针对特定用户进行 API 调用。如果您使用 Laravel 与此包交互,则授权流程已经为您构建。如果您不使用 Laravel,请查看 Http\Middleware\Filter 以了解我们如何将用户重定向到 Halo 服务器以请求代码,以及查看 Http\Controllers\HaloController 以了解我们如何将代码转换为令牌。该流程由 PKCE 保护。客户端凭证方法用于作为特定用户进行 API 调用,对于后台进程非常有用。

注意:您可以在同一个项目中使用任何一种方法或两种方法。

授权代码

有一个名为 Halo 的中间件,您可以将它应用到任何需要验证用户是否有 halo_token 的路由上。如果用户没有 halo_token,则会将用户重定向到包含 client_id 的 Halo OAuth 页面,用户可以选择与您的应用程序关联的团队。选择团队后,用户将被重定向到名为 halo.sso.redirect_uri 的路由,系统将 code 转换为令牌并将其保存到用户处。在保存 halo_token 之后,用户将被重定向到由中间件保护的初始页面。

注意:您需要在路由上设置 auth 中间件,因为需要 User 来检查是否存在 halo_token

注意:目前还没有一种方法可以删除已失效的令牌,因此您需要删除用户上的 halo_token 来重新启动流程。

客户端凭证

当使用 Client Credentials 时,您可以在不需要任何用户界面或用户交互的情况下进行 API 调用。

php artisan tinker
Psy Shell v0.11.14 (PHP 8.2.4 — cli) by Justin Hileman
> $halo = app(Spinen\Halo\Api\Client::class);
= Spinen\Halo\Api\Client {#4013}

> $halo->get('tenant')
= [
    "id" => 0,
    "key" => "some_key",
    "hostname" => "some.host.tld",
    "api_root" => "https://some.host.tld/api",
    "alias" => "",
    "linked_instance_id" => 0,
    "has_linked_instances" => false,
    "isportal" => false,
  ]

用法

Spinen\Halo\Api\Client 支持的操作

  • delete(string $path) - 使用 'DELETE' 作为最后一个参数的 request() 方法的快捷方式

  • generateProofKey(int $length = 30) - 生成 PKCE 需要的密钥

  • get(string $path) - 使用 'GET' 作为最后一个参数的 request() 方法的快捷方式

  • getToken() - 获取、返回或刷新令牌

  • oauthRequestTokenUsingAuthorizationCode(string $code, string $uri, ?string $verifier = null, ?string $scope = null) - 将 OAuth 代码转换为用于用户的范围令牌

  • oauthRequestTokenUsingClientCredentials(?string $scope = null) - 通过客户端凭据请求范围令牌

  • oauthUri($url) - 构建设置 redirect_url 为 $url 的 OAuth 页面的 URI

  • post(string $path, array $data) - 使用 'POST' 作为最后一个参数的 request() 方法的快捷方式

  • put(string $path, array $data) - 使用 'PUT' 作为最后一个参数的 request() 方法的快捷方式

  • request(?string $path, ?array $data = [], ?string $method = 'GET') - 使用 JWT 为已登录用户对 $path 进行 API 调用。

  • setConfigs(array $configs) - 验证并设置配置

  • setDebug(bool $debug) - 将 Guzzle 设置为调试模式

  • setToken(Token $token) - 设置 Halo API 的令牌

  • uri(?string $path = null, ?string $url = null) - 为 Halo API 的路径生成完整 URI。

  • validToken(?string $scope = null) - 令牌是否有效,如果提供了范围,令牌是否被批准用于该范围

使用客户端

客户端旨在模拟 Laravel 的 Eloquent 模型。当与 Halo 资源一起工作时,您可以像在 Laravel 中一样访问属性和关系。

获取客户端对象

当使用 Authorization Code

通过运行此包中包含的迁移,您的 User 类将有一个 halo_token 列。当您设置用户的令牌时,它将使用 Laravel 的加密方法在您的数据库中进行加密。设置 Halo API 令牌后,您可以通过 $user->halo 访问客户端对象。

php artisan tinker
Psy Shell v0.11.14 (PHP 8.2.4 — cli) by Justin Hileman
> $user = App\Models\User::find(1)
= App\Models\User {#4344
    id: 1,
    name: "Jimmy",
    email: "jimmy.puckett@spinen.com",
    email_verified_at: null,
    ...
> $user->halo;
= Spinen\Halo\Api\Client {#4748}

> $user->halo();
= Spinen\Halo\Support\Builder {#4706}

模型

API响应被转换为具有属性的类型,这些类型在Halo API文档中定义。您可以在src/文件夹中查看这些模型。每个模型上都有一个名为casts的属性,它指导客户端如何将API响应中的属性转换为相应的类型。如果casts属性为空,则表示API文档中没有定义这些属性,因此将返回一个数组。

注意:由于Halo处于活跃开发中,模型上的文档属性可能会过时。

> $user->halo()->teams->first()
= Spinen\Halo\Team {#4967
    +exists: true,
    +incrementing: false,
    +parentModel: null,
    +wasRecentlyCreated: false,
    +timestamps: false,
  }

> $user->halo()->teams->first()->toArray()
= [
    "id" => 1,
    "guid" => "<masked>",
    "name" => "1st Line Support",
    "sequence" => 10,
    "forrequests" => true,
    "foropps" => false,
    "forprojects" => false,
    "ticket_count" => 0,
    "department_id" => 3,
    "department_name" => "SPINEN - Support",
    "inactive" => false,
    "override_column_id" => 0,
    "teamphotopath" => "",
    "use" => "team",
  ]

关系

一些响应包含相关资源的链接。如果一个属性有关系,您可以通过调用它作为方法来访问它,并且会自动执行额外的调用并返回。值将存储在原始数据的位置,因此一旦加载就会缓存。


您也可以将这些关系称为属性,客户端会为您返回一个Collection(就像Eloquent一样)。


集合

结果被包装在Spinen\Halo\Support\Collection中,它扩展了Illuminate\Support\Collection,因此您可以使用任何文档中记录的集合辅助方法Laravel Collection方法

使用 "where" 过滤

您可以使用模型上的where进行筛选。第一个参数是要筛选的属性。第二个参数是可选的,表示要筛选的属性的值。如果它留空,则默认为true,因此它变为where('<property', true)。所有这些值都通过查询字符串传递。

有几个“辅助”方法是对where筛选的别名,以使调用更加明确。

  • whereId('<id>')where('id', '<id>')的别名。
  • whereNot('<property>')where('<property', false)的别名。

注意:Halo的API需要字符串“true”/“false”来表示布尔值,这在构建查询字符串时自动转换。


搜索

您可以使用search在端点上执行简单的搜索。有些端点允许搜索特定字段,您可以通过search_some_field('<for>')searchSomeField('<for>')访问这些字段。

> $user->halo()->clients->count()
= 9

// Only clients with a "y" in the name
> $user->halo()->clients()->search('y')->get()->pluck('name')
// Same as: $user->halo()->clients()->where('search', 'y')->get()->pluck('name')
= Spinen\Halo\Support\Collection {#5136
    all: [
      "Terry's Chocolates",
      "Tony's Tyre Emporium",
    ],
  }

限制返回的记录

您可以在构建器上调用takelimit方法(takelimit的别名)来限制返回的记录数到指定的计数参数。

> $builder->tickets()->take(7)->get()
= Spinen\Halo\Support\Collection {#4999
    all: [
      Spinen\Halo\Ticket {#4991
        +exists: true,
        +incrementing: false,
        +parentModel: null,
        +wasRecentlyCreated: false,
        +timestamps: false,
      },
      // more...
    ],
  }

> $tickets->count()
= 7

排序

您可以使用orderByorderByDesc方法对API的结果进行排序。将您希望排序结果的列作为第一个参数传递。orderByDesc('<column>')orderBy('<column>', 'desc')的别名。此外,您可以使用latestoldest来应用orderByorderByDesc,默认使用模型中表示创建记录时间的列。您可以将不同的列传递给这两个方法之一以覆盖默认列。

// Running through map to convert date to string
> $builder->tickets()->take(5)->oldest()->get()->pluck('dateoccurred', 'id')->map(fn($d) => (string)$d)
= Spinen\Halo\Support\Collection {#4983
    all: [
      1125 => "2019-12-14 13:30:00",
      1128 => "2019-12-14 13:30:00",
      1131 => "2019-12-14 13:30:00",
      1134 => "2019-12-14 13:30:00",
      1137 => "2019-12-14 13:30:00",
    ],
  }

> $builder->tickets()->take(5)->latest()->get()->pluck('dateoccurred', 'id')->map(fn($d) => (string)$d)
= Spinen\Halo\Support\Collection {#4763
    all: [
      2205 => "2021-03-24 11:35:40",
      2206 => "2021-03-24 10:23:47",
      2200 => "2021-03-23 16:44:00",
      2186 => "2021-03-23 14:17:57",
      2187 => "2021-03-23 14:17:57",
    ],
  }

注意:用于latest的列由模型上的CREATED_AT const控制。

分页

一些端点支持分页。您可以使用简单的分页通过将paginationpageination与一个可选的大小值链接到构建器。您可以使用page方法获取特定页面,该方法接受页面号作为参数。您可以通过将分页大小作为第二个参数传递给page方法来压缩调用。

// Could have been $builder->users()->paginate(2)->page(2)->get()
> $builder->users()->page(3, 2)->get()
= Spinen\Halo\Support\Collection {#4761
    all: [
      Spinen\Halo\User {#4763
        +exists: true,
        +incrementing: false,
        +parentModel: null,
        +wasRecentlyCreated: false,
        +timestamps: false,
      },
      // more...
    ],
  }

> $users->count()
= 2

更多示例

> $user->halo()->quotes->count()
= 4

$user->halo()->statuses->pluck('name', 'id')->sort()
= Spinen\Halo\Support\Collection {#4959
    all: [
      3 => "Action Required",
      18 => "Approved",
      17 => "Awaiting Approval",
      9 => "Closed",
      15 => "Closed Item",
      13 => "Closed Order",
      20 => "Completed",
      2 => "In Progress",
      16 => "Invoiced",
      1 => "New",
      21 => "On Hold",
      14 => "Open Item",
      12 => "Open Order",
      19 => "Rejected",
      22 => "Updated",
      10 => "With CAB",
      5 => "With Supplier",
      4 => "With User",
    ],
  }

打开项目

  • 在模型中设置关系
  • 在模型中添加获取器
  • 在模型上添加作用域

已知问题