einar-hansen/php-football-data

一个用于使用football-data.org API的PHP服务。

v0.2.0 2024-07-18 07:30 UTC

This package is auto-updated.

Last update: 2024-09-18 07:58:32 UTC


README

Latest Version on Packagist License Total Downloads

此包让您可以快速使用PHP访问FootballData的API。本实现使用API的V4(最新)版本,截至编写本文档时。有关API的更多信息,请参阅快速入门指南

安装

您可以通过composer安装此包

composer require einar-hansen/php-football-data

入门

您应该在https://www.football-data.org注册一个账户。

要使用此服务,您需要创建一个新的实例EinarHansen\FootballData\FootballDataService::class。该服务需要一个实现Psr\Http\Client\ClientInterface::class的客户端,例如GuzzleHttp\Client::class(不包括在包中)。

初始化

初始化API服务时,您无需传递任何参数。但是,您应该提供在注册时收到的API令牌,以避免访问限制和重速率限制。

 public function __construct(
    ?string $apiToken = null,
    string $baseUri = 'https://api.football-data.org',
    \Psr\Http\Client\ClientInterface $client = null,
    \Psr\Http\Message\RequestFactoryInterface $requestFactory = null,
    \Psr\Http\Message\UriFactoryInterface $uriFactory = null,
    \Psr\Http\Message\StreamFactoryInterface $streamFactory = null,
    \EinarHansen\Http\Contracts\Collection\CollectionFactory $collectionFactory = null,
    \EinarHansen\Http\Contracts\RateLimit\RateLimiterState $rateLimiterState = null
){}

由于PHP PSR支持以及Psr17FactoryDiscovery|Psr18ClientDiscovery,我们不需要传递PSR合约的实现。

集合

如果您想将返回的数据集合用作生成器,则可以传递一个EinarHansen\Http\Collection\LazyCollectionFactory::class类的实例,或者如果您想的话,可以传递一个实现EinarHansen\Http\Contracts\Collection\CollectionFactory::class的实例。

$service = new FootballDataService(
    collectionFactory: new \EinarHansen\Http\Collection\LazyCollectionFactory()
);

速率限制

如果您想跟踪您的速率限制,则还应提供一个\EinarHansen\Http\Contracts\RateLimit\RateLimiterState::class的实例。默认情况下,服务将记住尝试和剩余请求,在对象的生命周期内。以下是一个示例,使用随包提供的Psr16RateLimitState,配置为免费层

请查看football-data定价页面以找到您层的速率限制。

速率限制器将使用响应头中的速率限制详细信息更新状态。如果您用完了所有尝试,则您的请求将返回false

$psr6Cache = new \Symfony\Component\Cache\Adapter\FilesystemAdapter();
$psr16Cache = new \Symfony\Component\Cache\Psr16Cache($psr6Cache);

// Or in Laravel, using redis
$psr16Cache = \Illuminate\Support\Facades\Cache::store('redis');

$service = new FootballDataService(
    client: new \GuzzleHttp\Client(),
    rateLimiterState: new \EinarHansen\Http\RateLimit\Psr16RateLimitState(
        cacheKey: 'football-data',  // The key you want to use, any string will do
        cache: $psr16Cache,         // An instance of PSR-16 cache
        maxAttempts: 10,            // 10 calls/minute
        decaySeconds: 60,
    )
);

关于资源

以这种方式初始化服务。

use EinarHansen\FootballData\FootballDataService;

$service = new FootballDataService(apiToken: '::api-token::');

服务由5个资源组成

每个资源至少包含方法allfindall将返回一个项目集合,而find将根据其id获取一个项目。

如果您正在尝试查找的项目不存在,则资源方法将返回null。如果您受到速率限制,则方法将返回false

区域资源

在官方文档中了解更多关于区域资源的信息。

// 👆 Use the $service initialized above
use EinarHansen\FootballData\Data\Area;
use EinarHansen\FootballData\Data\Competition;
use EinarHansen\FootballData\Resources\AreaResource;

$resource = $service->areas(): AreaResource
$collection = $service->areas()->all(): iterable<int, Area>|false;
$area = $service->areas()->find(int $areaId): ?Area|false;

// You can also use the area resource to search for it's competitions
$competitions = $resource->competitions(Area|int $areaId): iterable<int, Competition>|false;

竞赛资源

在官方文档中了解更多关于竞赛资源的信息。

// 👆 Use the $service initialized above
use DateTimeInterface;
use EinarHansen\FootballData\Data\Competition;
use EinarHansen\FootballData\Data\FootballMatch;
use EinarHansen\FootballData\Data\Person;
use EinarHansen\FootballData\Data\PersonGoalScore;
use EinarHansen\FootballData\Data\Standing;
use EinarHansen\FootballData\Data\Team;
use EinarHansen\FootballData\Enums\Stage;
use EinarHansen\FootballData\Enums\Status;
use EinarHansen\FootballData\Resources\CompetitionResource;

$resource = $service->competitions(): CompetitionResource;

$collection = $resource->all(int|array $areaIds = null): iterable<int, Competition>|false;
$competition = $resource->find(int $competitionId): ?Competition|false;

// You can also use the competition resource to search for it's matches
$matches = $resource->competitions()->matches(
    Competition|int $competitionId,
    string|DateTimeInterface $dateFrom = null,
    string|DateTimeInterface $dateTo = null,
    Status $status = null,
    Stage $stage = null,
    int $matchday = null,
    string $group = null,
    int $season = null
): iterable<int, FootballMatch>|false;

// Or the current standings
$standings = $resource->competitions()->standings(
    Competition|int $competitionId,
    int $season = null,
    int $matchday = null,
    string|DateTimeInterface $date = null
): iterable<int, Standing>|false;

// Or the teams
$teams = $resource->competitions()->teams(
    Competition|int $competitionId,
    int $season = null,
): iterable<int, Team>|false;

// Or the topScorers
$topScorers = $resource->competitions()->topScorers(
    Competition|int $competitionId,
    int $season = null,
    int $limit = null,
): iterable<int, PersonGoalScore>|false;

比赛资源

在官方文档中了解更多关于比赛资源的信息。

// 👆 Use the $service initialized above
use DateTimeInterface;
use EinarHansen\FootballData\Data\FootballMatch;
use EinarHansen\FootballData\Enums\Status;
use EinarHansen\FootballData\Resources\MatchResource;

$resource = $service->matches(): MatchResource
$collection = $resource->all(
    array|int $matchIds = null,
    string|DateTimeInterface $dateFrom = null,
    string|DateTimeInterface $dateTo = null,
    Status $status = null,
    array|int $competitionIds = null,
): iterable<int, FootballMatch>|false;
$match = $service->matches()->find(int $matchId): ?FootballMatch|false;

// And you can show the previous matches between the teams
$matches = $service->matches()->matchHead2Head(
    int $matchId,
    string|DateTimeInterface $dateFrom = null,
    string|DateTimeInterface $dateTo = null,
    array|int $competitionIds = null,
    int $limit = null,
): iterable<int, FootballMatch>|false;

人物资源

在官方文档中了解更多关于人物资源的信息。

// 👆 Use the $service initialized above
use DateTimeInterface;
use EinarHansen\FootballData\Data\FootballMatch;
use EinarHansen\FootballData\Data\Person;
use EinarHansen\FootballData\Enums\Status;
use EinarHansen\FootballData\Resources\PersonResource;

$resource = $service->persons(): PersonResource
$person = $resource->find(int $personId): ?Person|false

// You can list matches the person was involved in
$matches = $resource->matches(
    Person|int $personId,
    string|DateTimeInterface $dateFrom = null,
    string|DateTimeInterface $dateTo = null,
    Status $status = null,
    array|int $competitionIds = null,
    int $limit = null,
    int $offset = null
): iterable<int, FootballMatch>|false;

队伍资源

在官方文档中了解更多关于团队资源的信息。

// 👆 Use the $service initialized above
use DateTimeInterface;
use EinarHansen\FootballData\Data\FootballMatch;
use EinarHansen\FootballData\Data\Person;
use EinarHansen\FootballData\Data\Team;
use EinarHansen\FootballData\Enums\Status;
use EinarHansen\FootballData\Resources\TeamResource;
use EinarHansen\FootballData\Pagination\TeamPaginator;

$resource = $service->teams(): TeamResource
$team = $resource->find(int $teamId): ?Team|false;

// This is the preferred method, as it returns a TeamPaginator
$collection = $resource->paginate(
    int $limit = 50,
    int $page = 1
): TeamPaginator<int, Team>|false;

// This method will iterate the results on every page until completed. 
// Set a max page count to avoid an uncontrollable loop.
$collection = $resource->all(int $maxPage = 10): iterable<int, Team>|false;

// You can list matches the person was involved in
$matches = $resource->matches(
    Person|int $personId,
    string|DateTimeInterface $dateFrom = null,
    string|DateTimeInterface $dateTo = null,
    Status $status = null,
    array|int $competitionIds = null,
    int $limit = null,
    int $offset = null
): iterable<int, FootballMatch>|false;

分页器

团队资源提供了一个分页方法,它返回一个实现EinarHansen\Http\Contracts\Pagination\Paginator::class合约的实例,用于跟踪页面和项目。团队资源使用EinarHansen\FootballData\Pagination\TeamPaginator::class。分页器允许您通过nextPagepreviousPage方法在不同页面之间跳转。

// Go to first page
$page1 = $service->teams()->paginate(limit: 50, page: 1);

// Jump between pages, every time you call this method a request is sent and items are loaded.
$page2 = $page1->nextPage();        // Paginator for page 2
$page3 = $page2->nextPage();        // Paginator for page 3
$page2 = $page3->previousPage();    // Paginator for page 2

$page2->items();        // array<int, Team>
$page2->count();        // (int) 50
$page2->isEmpty();      // (bool) false
$page2->isNotEmpty();   // (bool) true
json_encode($page2);    // string containing all items as an array

Laravel

如果您使用Laravel,则可能需要在.env文件中添加您的API Token,并在配置文件中引用它。您还可能希望将其注册为服务提供者中的单例,例如App\Providers\AppServiceProvider::class

use EinarHansen\FootballData\FootballDataService;
use EinarHansen\Http\RateLimit\Psr16RateLimitState;
use GuzzleHttp\Client;

...
class AppServiceProvider extends ServiceProvider

public function register()
{
    $this->app->singleton(FootballDataService::class, function ($app) {
        return new FootballDataService(
            apiToken: $app['config']['services']['football-data']['api-token'],
            client: new Client(),
            rateLimiterState: new Psr16RateLimitState(
                cacheKey: 'football-data', 
                cache: $app->make('cache.store'), 
                maxAttempts: 10,            
                decaySeconds: 60,
            )
        );
    });
...
}

致谢

本包使用了以下代码并从中得到很大启发:

测试

本包需要PHP8.1。如果您没有安装此版本或作为默认PHP版本,则可以使用bin/develop辅助脚本。该脚本灵感来源于Laravel Sail,但更加简单。要使用此脚本,您应该已经安装了Docker。它将为您拉取PHP8.1,并允许您运行下面的测试命令。

要使用脚本

# Enable helper script
chmod +x bin/develop

# Install PHP dependencies
bin/develop composer install

# Run code style formatting
bin/develop format

# Run static analysis
bin/develop analyse

# Run tests
bin/develop test

测试环境使用guzzlehttp/guzzle。我在将fixture-responses加载到测试环境中的ResponseInterface时遇到了一些问题,因为从本地文件创建的body流与halaxa/json-machine包不兼容。Nyholm包在生产环境和真实响应中运行良好。

关于

Einar Hansen是挪威奥斯陆的一名网络开发者。您可以在我的网站上找到更多关于我的信息。

许可证

MIT许可证(MIT)。