mirror of
https://github.com/archtechx/jobpipeline.git
synced 2025-12-12 09:24:02 +00:00
Initial commit
This commit is contained in:
commit
90c59626c2
8 changed files with 5460 additions and 0 deletions
20
.github/workflows/ci.yml
vendored
Normal file
20
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on: [ push, pull_request ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
laravel: ["^6.0", "^7.0"]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install dependencies
|
||||||
|
run: composer install
|
||||||
|
- name: Start Redis
|
||||||
|
uses: supercharge/redis-github-action@1.1.0
|
||||||
|
- name: Run tests
|
||||||
|
run: phpunit
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/vendor/
|
||||||
29
composer.json
Normal file
29
composer.json
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"name": "stancl/jobpipeline",
|
||||||
|
"description": "Turn any series of jobs into Laravel listeners.",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Samuel Štancl",
|
||||||
|
"email": "samuel.stancl@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Stancl\\JobPipeline\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Stancl\\JobPipeline\\Tests\\": "tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"illuminate/support": "^7.11"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"orchestra/testbench": "^5.2",
|
||||||
|
"spatie/valuestore": "^1.2",
|
||||||
|
"ext-redis": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
5085
composer.lock
generated
Normal file
5085
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
0
docker-compose.yml
Normal file
0
docker-compose.yml
Normal file
35
phpunit.xml
Normal file
35
phpunit.xml
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<phpunit backupGlobals="false"
|
||||||
|
backupStaticAttributes="false"
|
||||||
|
bootstrap="vendor/autoload.php"
|
||||||
|
colors="true"
|
||||||
|
convertErrorsToExceptions="true"
|
||||||
|
convertNoticesToExceptions="true"
|
||||||
|
convertWarningsToExceptions="true"
|
||||||
|
processIsolation="false"
|
||||||
|
stopOnFailure="false">
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="Unit">
|
||||||
|
<directory suffix="Test.php">./tests</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
<filter>
|
||||||
|
<whitelist processUncoveredFilesFromWhitelist="true">
|
||||||
|
<directory suffix=".php">./src</directory>
|
||||||
|
<exclude>
|
||||||
|
<file>./src/routes.php</file>
|
||||||
|
</exclude>
|
||||||
|
</whitelist>
|
||||||
|
</filter>
|
||||||
|
<php>
|
||||||
|
<env name="APP_ENV" value="testing"/>
|
||||||
|
<env name="BCRYPT_ROUNDS" value="4"/>
|
||||||
|
<env name="CACHE_DRIVER" value="redis"/>
|
||||||
|
<env name="MAIL_DRIVER" value="array"/>
|
||||||
|
<env name="QUEUE_CONNECTION" value="sync"/>
|
||||||
|
<env name="SESSION_DRIVER" value="array"/>
|
||||||
|
<env name="DB_CONNECTION" value="sqlite"/>
|
||||||
|
<env name="DB_DATABASE" value=":memory:"/>
|
||||||
|
<env name="AWS_DEFAULT_REGION" value="us-west-2"/>
|
||||||
|
</php>
|
||||||
|
</phpunit>
|
||||||
95
src/JobPipeline.php
Normal file
95
src/JobPipeline.php
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Stancl\JobPipeline;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
|
||||||
|
class JobPipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
/** @var bool */
|
||||||
|
public static $shouldBeQueuedByDefault = false;
|
||||||
|
|
||||||
|
/** @var callable[]|string[] */
|
||||||
|
public $jobs;
|
||||||
|
|
||||||
|
/** @var callable|null */
|
||||||
|
public $send;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A value passed to the jobs. This is the return value of $send.
|
||||||
|
*/
|
||||||
|
public $passable;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
public $shouldBeQueued;
|
||||||
|
|
||||||
|
public function __construct($jobs, callable $send = null, bool $shouldBeQueued = null)
|
||||||
|
{
|
||||||
|
$this->jobs = $jobs;
|
||||||
|
$this->send = $send ?? function ($event) {
|
||||||
|
// If no $send callback is set, we'll just pass the event through the jobs.
|
||||||
|
return $event;
|
||||||
|
};
|
||||||
|
$this->shouldBeQueued = $shouldBeQueued ?? static::$shouldBeQueuedByDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param callable[]|string[] $jobs */
|
||||||
|
public static function make(array $jobs): self
|
||||||
|
{
|
||||||
|
return new static($jobs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function send(callable $send): self
|
||||||
|
{
|
||||||
|
$this->send = $send;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shouldBeQueued(bool $shouldBeQueued)
|
||||||
|
{
|
||||||
|
$this->shouldBeQueued = $shouldBeQueued;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
foreach ($this->jobs as $job) {
|
||||||
|
app()->call([new $job(...$this->passable), 'handle']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a closure that can be used as a listener.
|
||||||
|
*/
|
||||||
|
public function toListener(): Closure
|
||||||
|
{
|
||||||
|
return function (...$args) {
|
||||||
|
$executable = $this->executable($args);
|
||||||
|
|
||||||
|
if ($this->shouldBeQueued) {
|
||||||
|
dispatch($executable);
|
||||||
|
} else {
|
||||||
|
dispatch_now($executable);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a serializable version of the current object.
|
||||||
|
*/
|
||||||
|
public function executable($listenerArgs): self
|
||||||
|
{
|
||||||
|
$clone = clone $this;
|
||||||
|
|
||||||
|
$passable = ($clone->send)(...$listenerArgs);
|
||||||
|
$passable = is_array($passable) ? $passable : [$passable];
|
||||||
|
|
||||||
|
$clone->passable = $passable;
|
||||||
|
unset($clone->send);
|
||||||
|
|
||||||
|
return $clone;
|
||||||
|
}
|
||||||
|
}
|
||||||
195
tests/JobPipelineTest.php
Normal file
195
tests/JobPipelineTest.php
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Stancl\JobPipeline\Tests;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
|
use Illuminate\Support\Facades\Queue;
|
||||||
|
use Orchestra\Testbench\TestCase;
|
||||||
|
use Spatie\Valuestore\Valuestore;
|
||||||
|
use Stancl\JobPipeline\JobPipeline;
|
||||||
|
|
||||||
|
class JobPipelineTest extends TestCase
|
||||||
|
{
|
||||||
|
public $mockConsoleOutput = false;
|
||||||
|
|
||||||
|
/** @var Valuestore */
|
||||||
|
protected $valuestore;
|
||||||
|
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
config(['queue.default' => 'redis']);
|
||||||
|
|
||||||
|
$this->valuestore = Valuestore::make(__DIR__ . '/tmp/jobpipelinetest.json')->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function job_pipeline_can_listen_to_any_event()
|
||||||
|
{
|
||||||
|
Event::listen(TestEvent::class, JobPipeline::make([
|
||||||
|
FooJob::class,
|
||||||
|
])->send(function () {
|
||||||
|
return $this->valuestore;
|
||||||
|
})->toListener());
|
||||||
|
|
||||||
|
$this->assertFalse($this->valuestore->has('foo'));
|
||||||
|
|
||||||
|
event(new TestEvent(new TestModel()));
|
||||||
|
|
||||||
|
$this->assertSame('bar', $this->valuestore->get('foo'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function job_pipeline_can_be_queued()
|
||||||
|
{
|
||||||
|
Queue::fake();
|
||||||
|
|
||||||
|
Event::listen(TestEvent::class, JobPipeline::make([
|
||||||
|
FooJob::class,
|
||||||
|
])->send(function () {
|
||||||
|
return $this->valuestore;
|
||||||
|
})->shouldBeQueued(true)->toListener());
|
||||||
|
|
||||||
|
Queue::assertNothingPushed();
|
||||||
|
|
||||||
|
event(new TestEvent(new TestModel()));
|
||||||
|
$this->assertFalse($this->valuestore->has('foo'));
|
||||||
|
|
||||||
|
Queue::pushed(JobPipeline::class, function (JobPipeline $pipeline) {
|
||||||
|
$this->assertSame([FooJob::class], $pipeline->jobs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function job_pipelines_run_when_queued()
|
||||||
|
{
|
||||||
|
Event::listen(TestEvent::class, JobPipeline::make([
|
||||||
|
FooJob::class,
|
||||||
|
])->send(function () {
|
||||||
|
return $this->valuestore;
|
||||||
|
})->shouldBeQueued(true)->toListener());
|
||||||
|
|
||||||
|
$this->assertFalse($this->valuestore->has('foo'));
|
||||||
|
event(new TestEvent(new TestModel()));
|
||||||
|
$this->artisan('queue:work --once');
|
||||||
|
|
||||||
|
$this->assertSame('bar', $this->valuestore->get('foo'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function job_pipeline_executes_jobs_and_passes_the_object_sequentially()
|
||||||
|
{
|
||||||
|
Event::listen(TestEvent::class, JobPipeline::make([
|
||||||
|
FirstJob::class,
|
||||||
|
SecondJob::class,
|
||||||
|
])->send(function (TestEvent $event) {
|
||||||
|
return [$event->testModel, $this->valuestore];
|
||||||
|
})->toListener());
|
||||||
|
|
||||||
|
$this->assertFalse($this->valuestore->has('foo'));
|
||||||
|
|
||||||
|
event(new TestEvent(new TestModel()));
|
||||||
|
|
||||||
|
$this->assertSame('first job changed property', $this->valuestore->get('foo'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function send_can_return_multiple_arguments()
|
||||||
|
{
|
||||||
|
Event::listen(TestEvent::class, JobPipeline::make([
|
||||||
|
JobWithMultipleArguments::class
|
||||||
|
])->send(function () {
|
||||||
|
return ['a', 'b'];
|
||||||
|
})->toListener());
|
||||||
|
|
||||||
|
$this->assertFalse(app()->bound('test_args'));
|
||||||
|
|
||||||
|
event(new TestEvent(new TestModel()));
|
||||||
|
|
||||||
|
$this->assertSame(['a', 'b'], app('test_args'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FooJob
|
||||||
|
{
|
||||||
|
protected $valuestore;
|
||||||
|
|
||||||
|
public function __construct(Valuestore $valuestore)
|
||||||
|
{
|
||||||
|
$this->valuestore = $valuestore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$this->valuestore->put('foo', 'bar');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class TestModel extends Model
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestEvent
|
||||||
|
{
|
||||||
|
/** @var TestModel $testModel */
|
||||||
|
public $testModel;
|
||||||
|
|
||||||
|
public function __construct(TestModel $testModel)
|
||||||
|
{
|
||||||
|
$this->testModel = $testModel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FirstJob
|
||||||
|
{
|
||||||
|
public $testModel;
|
||||||
|
|
||||||
|
public function __construct(TestModel $testModel)
|
||||||
|
{
|
||||||
|
$this->testModel = $testModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$this->testModel->foo = 'first job changed property';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SecondJob
|
||||||
|
{
|
||||||
|
public $testModel;
|
||||||
|
|
||||||
|
protected $valuestore;
|
||||||
|
|
||||||
|
public function __construct(TestModel $testModel, Valuestore $valuestore)
|
||||||
|
{
|
||||||
|
$this->testModel = $testModel;
|
||||||
|
$this->valuestore = $valuestore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$this->valuestore->put('foo', $this->testModel->foo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class JobWithMultipleArguments
|
||||||
|
{
|
||||||
|
protected $first;
|
||||||
|
protected $second;
|
||||||
|
|
||||||
|
public function __construct($first, $second)
|
||||||
|
{
|
||||||
|
$this->first = $first;
|
||||||
|
$this->second = $second;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
// we dont queue this job so no need to use valuestore here
|
||||||
|
app()->instance('test_args', [$this->first, $this->second]);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue