einar-hansen / php-football-data
一个用于使用football-data.org API的PHP服务。
Requires
- php: ^8.1
- einar-hansen/http-sdk: ^0.5.0
- halaxa/json-machine: ^1.1
- psr/http-client-implementation: ^1.0
Requires (Dev)
- guzzlehttp/guzzle: ^7.5
- laravel/pint: ^1.0
- php-http/message: ^1.0
- php-http/mock-client: ^1.0
- phpstan/extension-installer: ^1.1
- phpstan/phpstan-deprecation-rules: ^1.0
- phpstan/phpstan-phpunit: ^1.0
- phpunit/phpunit: ^10.0
- symfony/http-client: ^6.1
- symfony/var-dumper: ^6.1
README
此包让您可以快速使用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个资源组成
每个资源至少包含方法all
和find
。all
将返回一个项目集合,而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
。分页器允许您通过nextPage
和previousPage
方法在不同页面之间跳转。
// 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)。