From 8bb520a2a6a628fea3933c37e795a6d2c9bac968 Mon Sep 17 00:00:00 2001 From: Auke Terpstra Date: Tue, 27 Feb 2024 22:08:14 +0100 Subject: [PATCH 1/6] [4.x-dev] Can use the task handler url from the job, if available. Otherwise it falls back to the configured handler url. --- src/CloudTasksQueue.php | 30 +++++++++++++++++---------- src/HasTaskHandlerUrl.php | 10 +++++++++ tests/QueueTest.php | 20 ++++++++++++++++-- tests/Support/CustomHandlerUrlJob.php | 27 ++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 src/HasTaskHandlerUrl.php create mode 100644 tests/Support/CustomHandlerUrlJob.php diff --git a/src/CloudTasksQueue.php b/src/CloudTasksQueue.php index 72edbd8..82d8f31 100644 --- a/src/CloudTasksQueue.php +++ b/src/CloudTasksQueue.php @@ -57,8 +57,8 @@ public function push($job, $data = '', $queue = null) $this->createPayload($job, $queue, $data), $queue, null, - function ($payload, $queue) { - return $this->pushRaw($payload, $queue); + function ($payload, $queue) use ($job) { + return $this->pushRaw($payload, $queue, ['job' => $job]); } ); } @@ -73,8 +73,9 @@ function ($payload, $queue) { public function pushRaw($payload, $queue = null, array $options = []) { $delay = ! empty($options['delay']) ? $options['delay'] : 0; + $job = $options['job'] ?? null; - return $this->pushToCloudTasks($queue, $payload, $delay); + return $this->pushToCloudTasks($queue, $payload, $delay, $job); } /** @@ -93,8 +94,8 @@ public function later($delay, $job, $data = '', $queue = null) $this->createPayload($job, $queue, $data), $queue, $delay, - function ($payload, $queue, $delay) { - return $this->pushToCloudTasks($queue, $payload, $delay); + function ($payload, $queue, $delay) use ($job) { + return $this->pushToCloudTasks($queue, $payload, $delay, $job); } ); } @@ -105,9 +106,10 @@ function ($payload, $queue, $delay) { * @param string|null $queue * @param string $payload * @param \DateTimeInterface|\DateInterval|int $delay + * @param string|object $job * @return string */ - protected function pushToCloudTasks($queue, $payload, $delay = 0) + protected function pushToCloudTasks($queue, $payload, $delay, mixed $job) { $queue = $queue ?: $this->config['queue']; @@ -122,7 +124,7 @@ protected function pushToCloudTasks($queue, $payload, $delay = 0) connectionName: $this->getConnectionName(), ); - $this->addPayloadToTask($payload, $task); + $this->addPayloadToTask($payload, $task, $job); // The deadline for requests sent to the app. If the app does not respond by // this deadline then the request is cancelled and the attempt is marked as @@ -184,7 +186,8 @@ private function enrichPayloadWithInternalData( return $payload; } - public function addPayloadToTask(array $payload, Task $task): Task + /** @param string|object $job */ + public function addPayloadToTask(array $payload, Task $task, mixed $job): Task { $headers = value($this->headers, $payload) ?: []; @@ -206,7 +209,7 @@ public function addPayloadToTask(array $payload, Task $task): Task $task->setAppEngineHttpRequest($appEngineRequest); } else { $httpRequest = new HttpRequest(); - $httpRequest->setUrl($this->getHandler()); + $httpRequest->setUrl($this->getHandler($job)); $httpRequest->setBody(json_encode($payload)); $httpRequest->setHttpMethod(HttpMethod::POST); $httpRequest->setHeaders($headers); @@ -237,13 +240,18 @@ public function release(CloudTasksJob $job, int $delay = 0): void $payload = $job->getRawBody(); - $options = ['delay' => $delay]; + $options = ['delay' => $delay, 'job' => $job]; $this->pushRaw($payload, $job->getQueue(), $options); } - public function getHandler(): string + /** @param string|object $job */ + public function getHandler(mixed $job): string { + if ($job instanceof HasTaskHandlerUrl) { + return $job->taskHandlerUrl(); + } + if (empty($this->config['handler'])) { $this->config['handler'] = request()->getSchemeAndHttpHost(); } diff --git a/src/HasTaskHandlerUrl.php b/src/HasTaskHandlerUrl.php new file mode 100644 index 0000000..418d3c4 --- /dev/null +++ b/src/HasTaskHandlerUrl.php @@ -0,0 +1,10 @@ +setConfigValue('handler', 'https://docker.for.mac.localhost:8081'); @@ -74,6 +74,22 @@ public function it_posts_to_the_correct_handler_url() }); } + #[Test] + public function it_posts_to_the_job_handler_url() + { + // Arrange + $this->setConfigValue('handler', 'https://docker.for.mac.localhost:8081'); + CloudTasksApi::fake(); + + // Act + $this->dispatch(new CustomHandlerUrlJob()); + + // Assert + CloudTasksApi::assertTaskCreated(function (Task $task): bool { + return $task->getHttpRequest()->getUrl() === 'https://example.com/api/my-custom-route'; + }); + } + #[Test] public function it_posts_the_serialized_job_payload_to_the_handler() { diff --git a/tests/Support/CustomHandlerUrlJob.php b/tests/Support/CustomHandlerUrlJob.php new file mode 100644 index 0000000..a55734f --- /dev/null +++ b/tests/Support/CustomHandlerUrlJob.php @@ -0,0 +1,27 @@ + Date: Tue, 27 Feb 2024 22:17:18 +0100 Subject: [PATCH 2/6] [4.x-dev] A job can also have task headers, which are merged with any other headers. --- src/CloudTasksQueue.php | 6 ++++++ src/HasTaskHeaders.php | 11 +++++++++++ tests/QueueTest.php | 20 ++++++++++++++++++++ tests/Support/CustomHeadersJob.php | 30 ++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+) create mode 100644 src/HasTaskHeaders.php create mode 100644 tests/Support/CustomHeadersJob.php diff --git a/src/CloudTasksQueue.php b/src/CloudTasksQueue.php index 82d8f31..71a6c8a 100644 --- a/src/CloudTasksQueue.php +++ b/src/CloudTasksQueue.php @@ -190,6 +190,12 @@ private function enrichPayloadWithInternalData( public function addPayloadToTask(array $payload, Task $task, mixed $job): Task { $headers = value($this->headers, $payload) ?: []; + if ($job instanceof HasTaskHeaders) { + $headers = [ + ...$headers, + ...$job->taskHeaders(), + ]; + } if (!empty($this->config['app_engine'])) { $path = \Safe\parse_url(route('cloud-tasks.handle-task'), PHP_URL_PATH); diff --git a/src/HasTaskHeaders.php b/src/HasTaskHeaders.php new file mode 100644 index 0000000..38c9b70 --- /dev/null +++ b/src/HasTaskHeaders.php @@ -0,0 +1,11 @@ + */ + public function taskHeaders(): array; +} \ No newline at end of file diff --git a/tests/QueueTest.php b/tests/QueueTest.php index 66b9713..03e792a 100644 --- a/tests/QueueTest.php +++ b/tests/QueueTest.php @@ -18,6 +18,7 @@ use Stackkit\LaravelGoogleCloudTasksQueue\CloudTasksApi; use Stackkit\LaravelGoogleCloudTasksQueue\Events\JobReleased; use Tests\Support\CustomHandlerUrlJob; +use Tests\Support\CustomHeadersJob; use Tests\Support\FailingJob; use Tests\Support\FailingJobWithExponentialBackoff; use Tests\Support\JobOutput; @@ -509,4 +510,23 @@ public function headers_can_be_added_to_the_task_with_job_context() return $task->getHttpRequest()->getHeaders()['X-MyHeader'] === SimpleJob::class; }); } + + #[Test] + public function job_headers_can_be_added_to_the_task() + { + // Arrange + CloudTasksApi::fake(); + + // Act + Queue::connection()->setTaskHeaders([ + 'X-MyHeader' => 'MyValue', + ]); + $this->dispatch((new CustomHeadersJob())); + + // Assert + CloudTasksApi::assertTaskCreated(function (Task $task): bool { + $headers = $task->getHttpRequest()->getHeaders(); + return $headers['X-MyHeader'] === 'MyValue' && $headers['X-MyJobHeader'] === 'MyJobValue'; + }); + } } diff --git a/tests/Support/CustomHeadersJob.php b/tests/Support/CustomHeadersJob.php new file mode 100644 index 0000000..05baca5 --- /dev/null +++ b/tests/Support/CustomHeadersJob.php @@ -0,0 +1,30 @@ + 'MyJobValue', + ]; + } +} \ No newline at end of file From 7a0f7dff5c66110c3269aff5b65f64ab5479fbaa Mon Sep 17 00:00:00 2001 From: Auke Terpstra Date: Wed, 13 Mar 2024 20:44:00 +0100 Subject: [PATCH 3/6] [4.x-dev] Can set the HandlerUrl using a Closure callback. If given, the handler url is used from that closure. --- src/CloudTasksQueue.php | 16 +++++++------- src/HasTaskHandlerUrl.php | 10 --------- src/HasTaskHeaders.php | 11 ---------- tests/QueueTest.php | 31 ++++++--------------------- tests/Support/CustomHandlerUrlJob.php | 27 ----------------------- tests/Support/CustomHeadersJob.php | 30 -------------------------- 6 files changed, 15 insertions(+), 110 deletions(-) delete mode 100644 src/HasTaskHandlerUrl.php delete mode 100644 src/HasTaskHeaders.php delete mode 100644 tests/Support/CustomHandlerUrlJob.php delete mode 100644 tests/Support/CustomHeadersJob.php diff --git a/src/CloudTasksQueue.php b/src/CloudTasksQueue.php index 71a6c8a..e45d78d 100644 --- a/src/CloudTasksQueue.php +++ b/src/CloudTasksQueue.php @@ -25,11 +25,17 @@ class CloudTasksQueue extends LaravelQueue implements QueueContract { private Closure | array $headers = []; + private static ?Closure $handlerUrlCallback = null; public function __construct(public array $config, public CloudTasksClient $client, public $dispatchAfterCommit = false) { // } + + public static function configureHandlerUrlUsing(Closure $callback): void + { + static::$handlerUrlCallback = $callback; + } /** * Get the size of the queue. @@ -190,12 +196,6 @@ private function enrichPayloadWithInternalData( public function addPayloadToTask(array $payload, Task $task, mixed $job): Task { $headers = value($this->headers, $payload) ?: []; - if ($job instanceof HasTaskHeaders) { - $headers = [ - ...$headers, - ...$job->taskHeaders(), - ]; - } if (!empty($this->config['app_engine'])) { $path = \Safe\parse_url(route('cloud-tasks.handle-task'), PHP_URL_PATH); @@ -254,8 +254,8 @@ public function release(CloudTasksJob $job, int $delay = 0): void /** @param string|object $job */ public function getHandler(mixed $job): string { - if ($job instanceof HasTaskHandlerUrl) { - return $job->taskHandlerUrl(); + if (static::$handlerUrlCallback) { + return (static::$handlerUrlCallback)($job); } if (empty($this->config['handler'])) { diff --git a/src/HasTaskHandlerUrl.php b/src/HasTaskHandlerUrl.php deleted file mode 100644 index 418d3c4..0000000 --- a/src/HasTaskHandlerUrl.php +++ /dev/null @@ -1,10 +0,0 @@ - */ - public function taskHeaders(): array; -} \ No newline at end of file diff --git a/tests/QueueTest.php b/tests/QueueTest.php index 03e792a..82c35c0 100644 --- a/tests/QueueTest.php +++ b/tests/QueueTest.php @@ -16,9 +16,8 @@ use Illuminate\Support\Facades\Queue; use PHPUnit\Framework\Attributes\Test; use Stackkit\LaravelGoogleCloudTasksQueue\CloudTasksApi; +use Stackkit\LaravelGoogleCloudTasksQueue\CloudTasksQueue; use Stackkit\LaravelGoogleCloudTasksQueue\Events\JobReleased; -use Tests\Support\CustomHandlerUrlJob; -use Tests\Support\CustomHeadersJob; use Tests\Support\FailingJob; use Tests\Support\FailingJobWithExponentialBackoff; use Tests\Support\JobOutput; @@ -76,18 +75,21 @@ public function it_posts_to_the_configured_handler_url() } #[Test] - public function it_posts_to_the_job_handler_url() + public function it_posts_to_the_callback_handler_url() { // Arrange $this->setConfigValue('handler', 'https://docker.for.mac.localhost:8081'); CloudTasksApi::fake(); + CloudTasksQueue::configureHandlerUrlUsing(static fn(SimpleJob $job) => 'https://example.com/api/my-custom-route?job=' . $job->id); // Act - $this->dispatch(new CustomHandlerUrlJob()); + $job = new SimpleJob(); + $job->id = 1; + $this->dispatch($job); // Assert CloudTasksApi::assertTaskCreated(function (Task $task): bool { - return $task->getHttpRequest()->getUrl() === 'https://example.com/api/my-custom-route'; + return $task->getHttpRequest()->getUrl() === 'https://example.com/api/my-custom-route?job=1'; }); } @@ -510,23 +512,4 @@ public function headers_can_be_added_to_the_task_with_job_context() return $task->getHttpRequest()->getHeaders()['X-MyHeader'] === SimpleJob::class; }); } - - #[Test] - public function job_headers_can_be_added_to_the_task() - { - // Arrange - CloudTasksApi::fake(); - - // Act - Queue::connection()->setTaskHeaders([ - 'X-MyHeader' => 'MyValue', - ]); - $this->dispatch((new CustomHeadersJob())); - - // Assert - CloudTasksApi::assertTaskCreated(function (Task $task): bool { - $headers = $task->getHttpRequest()->getHeaders(); - return $headers['X-MyHeader'] === 'MyValue' && $headers['X-MyJobHeader'] === 'MyJobValue'; - }); - } } diff --git a/tests/Support/CustomHandlerUrlJob.php b/tests/Support/CustomHandlerUrlJob.php deleted file mode 100644 index a55734f..0000000 --- a/tests/Support/CustomHandlerUrlJob.php +++ /dev/null @@ -1,27 +0,0 @@ - 'MyJobValue', - ]; - } -} \ No newline at end of file From fa0d67545acf7d837f6324399e8ff14b15055ec6 Mon Sep 17 00:00:00 2001 From: Auke Terpstra Date: Wed, 13 Mar 2024 20:49:52 +0100 Subject: [PATCH 4/6] [4.x-dev] Import class --- tests/QueueTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/QueueTest.php b/tests/QueueTest.php index b236aa8..82c35c0 100644 --- a/tests/QueueTest.php +++ b/tests/QueueTest.php @@ -16,6 +16,7 @@ use Illuminate\Support\Facades\Queue; use PHPUnit\Framework\Attributes\Test; use Stackkit\LaravelGoogleCloudTasksQueue\CloudTasksApi; +use Stackkit\LaravelGoogleCloudTasksQueue\CloudTasksQueue; use Stackkit\LaravelGoogleCloudTasksQueue\Events\JobReleased; use Tests\Support\FailingJob; use Tests\Support\FailingJobWithExponentialBackoff; From 4cf5598db6ed35def3ceaaf37fd062bcf174af07 Mon Sep 17 00:00:00 2001 From: Auke Terpstra Date: Wed, 27 Mar 2024 20:25:50 +0100 Subject: [PATCH 5/6] [4.x-dev] Added missing test attribute --- tests/Support/SimpleJob.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Support/SimpleJob.php b/tests/Support/SimpleJob.php index e554d24..d443116 100644 --- a/tests/Support/SimpleJob.php +++ b/tests/Support/SimpleJob.php @@ -15,6 +15,7 @@ class SimpleJob implements ShouldQueue use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public $tries = 3; + public $id = 0; /** * Create a new job instance. From 829f7205de45a095aa150d94e56ff7ab8997e2f1 Mon Sep 17 00:00:00 2001 From: Auke Terpstra Date: Wed, 27 Mar 2024 20:27:06 +0100 Subject: [PATCH 6/6] [4.x-dev] Added "setTaskHeadersUsing()" method to allow a closure to be passed for the task headers. This is the same as the "configureHandlerUrlUsing()" method. Also added some forget methods so these callbacks can be easily cleared if necessary. --- README.md | 32 +++++++++++++++++++++++++------- src/CloudTasksQueue.php | 31 +++++++++++++++++++++++++++---- tests/QueueTest.php | 20 ++++++++++++++------ 3 files changed, 66 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 44586fa..749b314 100644 --- a/README.md +++ b/README.md @@ -102,26 +102,44 @@ Please check the table below on what the values mean and what their value should #### Passing headers to a task -You can pass headers to a task by using the `withHeaders` method on the queue connection. +You can pass headers to a task by using the `setTaskHeadersUsing` method on the `CloudTasksQueue` class. ```php -use Illuminate\Queue\Queue; +use Stackkit\LaravelGoogleCloudTasksQueue\CloudTasksQueue; -Queue::connection()->setTaskHeaders([ +CloudTasksQueue::setTaskHeadersUsing(static fn() => [ 'X-My-Header' => 'My-Value', ]); ``` -If necessary, the current job being dispatched is also available: +If necessary, the current payload being dispatched is also available: ```php -use Illuminate\Queue\Queue; +use Stackkit\LaravelGoogleCloudTasksQueue\CloudTasksQueue; -Queue::connection()->setTaskHeaders(fn (array $job) => [ - 'X-My-Header' => $job['displayName'] +CloudTasksQueue::setTaskHeadersUsing(static fn(array $payload) => [ + 'X-My-Header' => $payload['displayName'], ]); ``` +#### Configure task handler url + +You can set the handler url for a task by using the `configureHandlerUrlUsing` method on the `CloudTasksQueue` class. + +```php +use Stackkit\LaravelGoogleCloudTasksQueue\CloudTasksQueue; + +CloudTasksQueue::configureHandlerUrlUsing(static fn() => 'https://example.com/my-url'); +``` + +If necessary, the current job being dispatched is also available: + +```php +use Stackkit\LaravelGoogleCloudTasksQueue\CloudTasksQueue; + +CloudTasksQueue::configureHandlerUrlUsing(static fn(MyJob $job) => 'https://example.com/my-url/' . $job->something()); +``` +
How it works & Differences diff --git a/src/CloudTasksQueue.php b/src/CloudTasksQueue.php index e491d00..a4980e5 100644 --- a/src/CloudTasksQueue.php +++ b/src/CloudTasksQueue.php @@ -24,8 +24,8 @@ class CloudTasksQueue extends LaravelQueue implements QueueContract { - private Closure|array $headers = []; private static ?Closure $handlerUrlCallback = null; + private static ?Closure $taskHeadersCallback = null; public function __construct(public array $config, public CloudTasksClient $client, public $dispatchAfterCommit = false) { @@ -37,6 +37,21 @@ public static function configureHandlerUrlUsing(Closure $callback): void static::$handlerUrlCallback = $callback; } + public static function forgetHandlerUrlCallback(): void + { + self::$handlerUrlCallback = null; + } + + public static function setTaskHeadersUsing(Closure $callback): void + { + static::$taskHeadersCallback = $callback; + } + + public static function forgetTaskHeadersCallback(): void + { + self::$taskHeadersCallback = null; + } + /** * Get the size of the queue. * @@ -194,7 +209,7 @@ private function enrichPayloadWithInternalData( /** @param string|object $job */ public function addPayloadToTask(array $payload, Task $task, mixed $job): Task { - $headers = value($this->headers, $payload) ?: []; + $headers = $this->headers($payload); if (! empty($this->config['app_engine'])) { $path = \Safe\parse_url(route('cloud-tasks.handle-task'), PHP_URL_PATH); @@ -270,8 +285,16 @@ public function getHandler(mixed $job): string return $handler.'/'.config('cloud-tasks.uri'); } - public function setTaskHeaders(Closure|array $headers): void + /** + * @param array $payload + * @return array + */ + private function headers(mixed $payload): array { - $this->headers = $headers; + if (!static::$taskHeadersCallback) { + return []; + } + + return (static::$taskHeadersCallback)($payload); } } diff --git a/tests/QueueTest.php b/tests/QueueTest.php index 82c35c0..a296032 100644 --- a/tests/QueueTest.php +++ b/tests/QueueTest.php @@ -14,6 +14,7 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Queue; +use Override; use PHPUnit\Framework\Attributes\Test; use Stackkit\LaravelGoogleCloudTasksQueue\CloudTasksApi; use Stackkit\LaravelGoogleCloudTasksQueue\CloudTasksQueue; @@ -28,6 +29,15 @@ class QueueTest extends TestCase { + #[Override] + protected function tearDown(): void + { + parent::tearDown(); + + CloudTasksQueue::forgetHandlerUrlCallback(); + CloudTasksQueue::forgetTaskHeadersCallback(); + } + #[Test] public function a_http_request_with_the_handler_url_is_made() { @@ -480,7 +490,7 @@ public function headers_can_be_added_to_the_task() CloudTasksApi::fake(); // Act - Queue::connection()->setTaskHeaders([ + CloudTasksQueue::setTaskHeadersUsing(static fn() => [ 'X-MyHeader' => 'MyValue', ]); @@ -499,11 +509,9 @@ public function headers_can_be_added_to_the_task_with_job_context() CloudTasksApi::fake(); // Act - Queue::connection()->setTaskHeaders(function (array $payload) { - return [ - 'X-MyHeader' => $payload['displayName'], - ]; - }); + CloudTasksQueue::setTaskHeadersUsing(static fn(array $payload) => [ + 'X-MyHeader' => $payload['displayName'], + ]); $this->dispatch((new SimpleJob()));