sammyjo20 / laravel-chunkable-jobs
将任务拆分为多个独立的任务块
Requires
- php: ^8.1
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.5
- orchestra/testbench: ^8.0 || ^9.0
- pestphp/pest: ^2.34
- spatie/ray: ^1.33
README
Laravel Chunkable Jobs
此包允许您将一个过程拆分为多个具有自己分块的作业。这对于处理大量数据非常完美,因为您可以委托处理到单独的作业中,或者如果您正在从分页API检索数据。它通过处理作业然后排队另一个作业以处理下一个分块,直到达到末尾。
示例
<?php use Sammyjo20\ChunkableJobs\Chunk; use Sammyjo20\ChunkableJobs\ChunkableJob; class GetPageOfPokemon extends ChunkableJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public function defineChunk(): ?Chunk { $response = Http::asJson()->get('https://pokeapi.co/api/v2/pokemon'); $count = $response->json('count'); // 1154 return new Chunk(totalItems: $count, chunkSize: 1, startingPosition: 1); } protected function handleChunk(Chunk $chunk): void { $response = Http::asJson()->get(sprintf('https://pokeapi.co/api/v2/pokemon?limit=%s&offset=%s', $chunk->limit, $chunk->offset)); $data = $response->json(); // Store data of response } }
安装
通过Composer安装此包。此包需要PHP 8.1+和Laravel 8或更高版本。
composer require sammyjo20/laravel-chunkable-jobs
入门
创建一个新作业并从作业中移除handle
方法。接下来,扩展ChunkableJob
类。您需要向您的类添加两个方法,一个defineChunk
方法和一个handleChunk
方法。在我的示例中,我将从Pokémon API获取每个Pokémon并将其存储到我的应用程序中。您应该有如下内容。
<?php use Sammyjo20\ChunkableJobs\Chunk; use Sammyjo20\ChunkableJobs\ChunkableJob; class GetPageOfPokemon extends ChunkableJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public function defineChunk(): ?Chunk { // } protected function handleChunk(Chunk $chunk): void { // } }
接下来,我们需要定义我们的块,这告诉可分块的作业它需要处理多少项以及块的大小,以便它知道运行“handleChunk”方法的次数。在方法内部,您可以返回一个块。这个块接受三个参数:totalItems
,chunkSize
和startingPosition
。如果您返回null或没有totalItems的块,handleChunk
将不会处理。
<?php use Sammyjo20\ChunkableJobs\Chunk; public function defineChunk(): ?Chunk { $response = Http::asJson()->get('https://pokeapi.co/api/v2/pokemon'); $count = $response->json('count'); // 1154 return new Chunk(totalItems: $count, chunkSize: 1, startingPosition: 1); }
块构造函数
- totalItems:您想要分块处理的项的数量,例如,如果您有100个项,块大小为10,它将创建10个块。
- chunkSize:每个块的大小。如果您处理分页API,则此大小与API的每页大小相同。
- startingPosition:块的开始位置,默认为1,但如果您想要恢复作业,可以更改此startingPosition。
接下来,我们想要编写处理每个块的逻辑。在我的示例中,我想为该块进行API调用然后存储响应。handleMethod
将在每个块上执行并将包含有关该块的有用信息。
<?php use Sammyjo20\ChunkableJobs\Chunk; protected function handleChunk(Chunk $chunk): void { $response = Http::asJson()->get(sprintf('https://pokeapi.co/api/v2/pokemon?limit=%s&offset=%s', $chunk->limit, $chunk->offset)); $data = $response->json(); // Store data of response }
块属性
- totalItems:创建块时提供的总项数。此属性不会改变。
- totalChunks:创建块时生成的块总数。此属性不会改变。
- remainingItems:块中剩余的项。此属性随着分块作业的派遣而减少。
- remainingChunks:剩余的块。此属性随着分块作业的派遣而减少。
- originalSize:块的大小。此属性不会改变。
- size:当前块的大小。此属性将在最后一块(如果有余数)时改变。
- limit:当前块的限制。类似于大小,它旨在帮助您与使用limit/offset操作API交互。
- offset:当前块的偏移量。此属性随着分块作业的派遣而增加。
- position:当前块的位置。它旨在作为处理分页API时的“页”。这将随着分块作业的派遣而增加。
- 元数据: 如果您希望对数据块应用任何元数据,则可以使用此数组。元数据将被传递到所有后续数据块。
数据块方法
- next: 允许您获取下一个数据块。这是一个不可变方法,因此原始对象不会被修改。
- move: 允许您移动到指定的数据块位置。默认情况下,它是不可变的,但您可以使其可变。
- replace: 允许您用另一个数据块替换当前对象。
- isFirst: 指定数据块是否是第一个数据块。
- isNotFirst: 与isFirst相反。
- isLast: 指定数据块是否是最后一个数据块。
- isNotLast: 与isFirst相反。
- isEmpty: 指定数据块是否为空,即totalItems属性为零。
- isNotEmpty: 与isEmpty相反。
分发
要分发可分块作业,操作完全相同。可分块作业的默认行为是处理一个作业,然后在成功处理后分发下一个作业。
<?php GetPageOfPokemon::dispatch();
一次性分发所有分块作业
有时您可能想将尽可能多的资源投入到特定的分块作业中。如果一次处理一个数据块不合适,而您希望立即分发所有数据块,可以使用分块作业上的静态方法dispatchAllChunks
。它将通过参数接受构造函数参数。或者,您还可以使用BulkChunkDispatcher
类。
<?php use Sammyjo20\ChunkableJobs\BulkChunkDispatcher; // Will dispatch all jobs at once 🚀 GetPageOfPokemon::dispatchAllChunks(); // or BulkChunkDispatcher::dispatch(new GetPageOfPokemon);
提前停止分块
有时您可能希望提前停止分块过程。您可以使用stopChunking
方法,作业将不会分发下一个数据块。
<?php use Sammyjo20\ChunkableJobs\Chunk; protected function handleChunk(Chunk $chunk): void { $response = Http::asJson()->get(sprintf('https://pokeapi.co/api/v2/pokemon?limit=%s&offset=%s', $chunk->limit, $chunk->offset)); // Stop chunking early... if ($response->failed()) { $this->stopChunking(); } }
自定义起始数据块
有时您可能希望在之前失败或暂停的地方恢复分块作业。您可以在分发作业之前在作业实例上设置数据块。
<?php use Sammyjo20\ChunkableJobs\Chunk; $job = new GetPageOfPokemon; $job->setChunk(new Chunk(totalItems: 100, chunkSize: 10, startingPosition: 5)); dispatch($job);
使用ChunkRange
遍历所有数据块
如果您需要遍历每个数据块,可以使用ChunkRange
类。这将返回一个生成器,您可以遍历以获取每个数据块。
use Sammyjo20\ChunkableJobs\ChunkRange; $chunkRange = ChunkRange::create(30, 10); foreach($chunkRange as $chunk) { // Handle $chunks }
未知大小分块
有时您可能不知道要分块的大小/限制,因此您想无限分块,并在达到限制时停止。如果您想这样做,可以使用UnknownSizeChunk
,它将大小设置为PHP_MAX_INT
(这是一个非常大的数字),您可以在任何时候停止。
<?php use Sammyjo20\ChunkableJobs\Chunk; use Sammyjo20\ChunkableJobs\ChunkableJob; use Sammyjo20\ChunkableJobs\UnknownSizeChunk; class GetPageOfPokemon extends ChunkableJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public function defineChunk(): ?Chunk { return UnknownSizeChunk(chunkSize: 100); } protected function handleChunk(Chunk $chunk): void { // Keep processing // When ready to stop: if ($stop === true) { $this->stopChunking(); } } }
设置下一个数据块
有时您可能想完全更改分块方式,如果这样做,您可以在分块时使用nextChunk
方法,下一个数据块将被此数据块替换。
<?php protected function handleChunk(Chunk $chunk): void { $chunk = new Chunk(100, 10) $chunk = $chunk->move(5); $this->setNextChunk($chunk); }
设置和拆卸
在分块过程前后调用setUp
和tearDown
方法。如果您需要在分块开始之前做一些设置,并在所有作业数据块处理完毕后进行一些清理,这非常有用。
<?php use Sammyjo20\ChunkableJobs\Chunk; use Sammyjo20\ChunkableJobs\ChunkableJob; use Illuminate\Support\Facades\Log; class GetPageOfPokemon extends ChunkableJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public function defineChunk(): ?Chunk { $response = Http::asJson()->get('https://pokeapi.co/api/v2/pokemon'); $count = $response->json('count'); // 1154 return new Chunk(totalItems: $count, chunkSize: 1, startingPosition: 1); } protected function handleChunk(Chunk $chunk): void { $response = Http::asJson()->get(sprintf('https://pokeapi.co/api/v2/pokemon?limit=%s&offset=%s', $chunk->limit, $chunk->offset)); $data = $response->json(); // Store data of response } protected function setUp(): void { Log::info('Starting the retrieval process...'); } protected function tearDown(): void { Log::info('Finished the retrieval process!'); } }
关于属性的警告
当您在作业上使用ChunkableJob类时,您必须注意在运行时在您的作业上设置的属性。当创建每个下一个分块作业时,当前对象将被克隆,包括其公共、受保护和私有属性。如果有任何属性是通过defineChunk
或handleChunk
方法设置的,并且您不希望它们与下一个数据块共享,请确保将属性添加到作业上的$ignoredProperties
数组中。
<?php use Sammyjo20\ChunkableJobs\Chunk; use Sammyjo20\ChunkableJobs\ChunkableJob; use Illuminate\Support\Facades\Log; class GetPageOfPokemon extends ChunkableJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected string $stripeSecret; protected array $ignoredProperties = ['stripeSecret']; }
由于私有属性不能被忽略,您应该使用modifyClone
方法,该方法可用于在将克隆对象放入队列之前取消设置私有属性或进行最终调整。
<?php class GetPageOfPokemon extends ChunkableJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; private ?string $privateProperty = null; public function __construct() { $this->privateProperty = 'Shh!'; } // ... Other methods protected function modifyClone(ChunkableJob $job): static { unset($job->privateProperty); return $job; } }