mirror of
https://github.com/archtechx/jobpipeline.git
synced 2025-12-12 06:44:03 +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