v1.0.0 initial release

This commit is contained in:
samy
2025-06-13 10:48:20 -10:00
commit 0c8f70bca5
3333 changed files with 189946 additions and 0 deletions
+506
View File
@@ -0,0 +1,506 @@
<?php
namespace Illuminate\Bus;
use Carbon\CarbonImmutable;
use Closure;
use Illuminate\Contracts\Queue\Factory as QueueFactory;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Queue\CallQueuedClosure;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use JsonSerializable;
use Throwable;
class Batch implements Arrayable, JsonSerializable
{
/**
* The queue factory implementation.
*
* @var \Illuminate\Contracts\Queue\Factory
*/
protected $queue;
/**
* The repository implementation.
*
* @var \Illuminate\Bus\BatchRepository
*/
protected $repository;
/**
* The batch ID.
*
* @var string
*/
public $id;
/**
* The batch name.
*
* @var string
*/
public $name;
/**
* The total number of jobs that belong to the batch.
*
* @var int
*/
public $totalJobs;
/**
* The total number of jobs that are still pending.
*
* @var int
*/
public $pendingJobs;
/**
* The total number of jobs that have failed.
*
* @var int
*/
public $failedJobs;
/**
* The IDs of the jobs that have failed.
*
* @var array
*/
public $failedJobIds;
/**
* The batch options.
*
* @var array
*/
public $options;
/**
* The date indicating when the batch was created.
*
* @var \Carbon\CarbonImmutable
*/
public $createdAt;
/**
* The date indicating when the batch was cancelled.
*
* @var \Carbon\CarbonImmutable|null
*/
public $cancelledAt;
/**
* The date indicating when the batch was finished.
*
* @var \Carbon\CarbonImmutable|null
*/
public $finishedAt;
/**
* Create a new batch instance.
*
* @param \Illuminate\Contracts\Queue\Factory $queue
* @param \Illuminate\Bus\BatchRepository $repository
* @param string $id
* @param string $name
* @param int $totalJobs
* @param int $pendingJobs
* @param int $failedJobs
* @param array $failedJobIds
* @param array $options
* @param \Carbon\CarbonImmutable $createdAt
* @param \Carbon\CarbonImmutable|null $cancelledAt
* @param \Carbon\CarbonImmutable|null $finishedAt
*/
public function __construct(
QueueFactory $queue,
BatchRepository $repository,
string $id,
string $name,
int $totalJobs,
int $pendingJobs,
int $failedJobs,
array $failedJobIds,
array $options,
CarbonImmutable $createdAt,
?CarbonImmutable $cancelledAt = null,
?CarbonImmutable $finishedAt = null,
) {
$this->queue = $queue;
$this->repository = $repository;
$this->id = $id;
$this->name = $name;
$this->totalJobs = $totalJobs;
$this->pendingJobs = $pendingJobs;
$this->failedJobs = $failedJobs;
$this->failedJobIds = $failedJobIds;
$this->options = $options;
$this->createdAt = $createdAt;
$this->cancelledAt = $cancelledAt;
$this->finishedAt = $finishedAt;
}
/**
* Get a fresh instance of the batch represented by this ID.
*
* @return self
*/
public function fresh()
{
return $this->repository->find($this->id);
}
/**
* Add additional jobs to the batch.
*
* @param \Illuminate\Support\Enumerable|object|array $jobs
* @return self
*/
public function add($jobs)
{
$count = 0;
$jobs = Collection::wrap($jobs)->map(function ($job) use (&$count) {
$job = $job instanceof Closure ? CallQueuedClosure::create($job) : $job;
if (is_array($job)) {
$count += count($job);
return with($this->prepareBatchedChain($job), function ($chain) {
return $chain->first()
->allOnQueue($this->options['queue'] ?? null)
->allOnConnection($this->options['connection'] ?? null)
->chain($chain->slice(1)->values()->all());
});
} else {
$job->withBatchId($this->id);
$count++;
}
return $job;
});
$this->repository->transaction(function () use ($jobs, $count) {
$this->repository->incrementTotalJobs($this->id, $count);
$this->queue->connection($this->options['connection'] ?? null)->bulk(
$jobs->all(),
$data = '',
$this->options['queue'] ?? null
);
});
return $this->fresh();
}
/**
* Prepare a chain that exists within the jobs being added.
*
* @param array $chain
* @return \Illuminate\Support\Collection
*/
protected function prepareBatchedChain(array $chain)
{
return (new Collection($chain))->map(function ($job) {
$job = $job instanceof Closure ? CallQueuedClosure::create($job) : $job;
return $job->withBatchId($this->id);
});
}
/**
* Get the total number of jobs that have been processed by the batch thus far.
*
* @return int
*/
public function processedJobs()
{
return $this->totalJobs - $this->pendingJobs;
}
/**
* Get the percentage of jobs that have been processed (between 0-100).
*
* @return int
*/
public function progress()
{
return $this->totalJobs > 0 ? round(($this->processedJobs() / $this->totalJobs) * 100) : 0;
}
/**
* Record that a job within the batch finished successfully, executing any callbacks if necessary.
*
* @param string $jobId
* @return void
*/
public function recordSuccessfulJob(string $jobId)
{
$counts = $this->decrementPendingJobs($jobId);
if ($this->hasProgressCallbacks()) {
$batch = $this->fresh();
(new Collection($this->options['progress']))->each(function ($handler) use ($batch) {
$this->invokeHandlerCallback($handler, $batch);
});
}
if ($counts->pendingJobs === 0) {
$this->repository->markAsFinished($this->id);
}
if ($counts->pendingJobs === 0 && $this->hasThenCallbacks()) {
$batch = $this->fresh();
(new Collection($this->options['then']))->each(function ($handler) use ($batch) {
$this->invokeHandlerCallback($handler, $batch);
});
}
if ($counts->allJobsHaveRanExactlyOnce() && $this->hasFinallyCallbacks()) {
$batch = $this->fresh();
(new Collection($this->options['finally']))->each(function ($handler) use ($batch) {
$this->invokeHandlerCallback($handler, $batch);
});
}
}
/**
* Decrement the pending jobs for the batch.
*
* @param string $jobId
* @return \Illuminate\Bus\UpdatedBatchJobCounts
*/
public function decrementPendingJobs(string $jobId)
{
return $this->repository->decrementPendingJobs($this->id, $jobId);
}
/**
* Determine if the batch has finished executing.
*
* @return bool
*/
public function finished()
{
return ! is_null($this->finishedAt);
}
/**
* Determine if the batch has "progress" callbacks.
*
* @return bool
*/
public function hasProgressCallbacks()
{
return isset($this->options['progress']) && ! empty($this->options['progress']);
}
/**
* Determine if the batch has "success" callbacks.
*
* @return bool
*/
public function hasThenCallbacks()
{
return isset($this->options['then']) && ! empty($this->options['then']);
}
/**
* Determine if the batch allows jobs to fail without cancelling the batch.
*
* @return bool
*/
public function allowsFailures()
{
return Arr::get($this->options, 'allowFailures', false) === true;
}
/**
* Determine if the batch has job failures.
*
* @return bool
*/
public function hasFailures()
{
return $this->failedJobs > 0;
}
/**
* Record that a job within the batch failed to finish successfully, executing any callbacks if necessary.
*
* @param string $jobId
* @param \Throwable $e
* @return void
*/
public function recordFailedJob(string $jobId, $e)
{
$counts = $this->incrementFailedJobs($jobId);
if ($counts->failedJobs === 1 && ! $this->allowsFailures()) {
$this->cancel();
}
if ($this->hasProgressCallbacks() && $this->allowsFailures()) {
$batch = $this->fresh();
(new Collection($this->options['progress']))->each(function ($handler) use ($batch, $e) {
$this->invokeHandlerCallback($handler, $batch, $e);
});
}
if ($counts->failedJobs === 1 && $this->hasCatchCallbacks()) {
$batch = $this->fresh();
(new Collection($this->options['catch']))->each(function ($handler) use ($batch, $e) {
$this->invokeHandlerCallback($handler, $batch, $e);
});
}
if ($counts->allJobsHaveRanExactlyOnce() && $this->hasFinallyCallbacks()) {
$batch = $this->fresh();
(new Collection($this->options['finally']))->each(function ($handler) use ($batch, $e) {
$this->invokeHandlerCallback($handler, $batch, $e);
});
}
}
/**
* Increment the failed jobs for the batch.
*
* @param string $jobId
* @return \Illuminate\Bus\UpdatedBatchJobCounts
*/
public function incrementFailedJobs(string $jobId)
{
return $this->repository->incrementFailedJobs($this->id, $jobId);
}
/**
* Determine if the batch has "catch" callbacks.
*
* @return bool
*/
public function hasCatchCallbacks()
{
return isset($this->options['catch']) && ! empty($this->options['catch']);
}
/**
* Determine if the batch has "finally" callbacks.
*
* @return bool
*/
public function hasFinallyCallbacks()
{
return isset($this->options['finally']) && ! empty($this->options['finally']);
}
/**
* Cancel the batch.
*
* @return void
*/
public function cancel()
{
$this->repository->cancel($this->id);
}
/**
* Determine if the batch has been cancelled.
*
* @return bool
*/
public function canceled()
{
return $this->cancelled();
}
/**
* Determine if the batch has been cancelled.
*
* @return bool
*/
public function cancelled()
{
return ! is_null($this->cancelledAt);
}
/**
* Delete the batch from storage.
*
* @return void
*/
public function delete()
{
$this->repository->delete($this->id);
}
/**
* Invoke a batch callback handler.
*
* @param callable $handler
* @param \Illuminate\Bus\Batch $batch
* @param \Throwable|null $e
* @return void
*/
protected function invokeHandlerCallback($handler, Batch $batch, ?Throwable $e = null)
{
try {
$handler($batch, $e);
} catch (Throwable $e) {
if (function_exists('report')) {
report($e);
}
}
}
/**
* Convert the batch to an array.
*
* @return array
*/
public function toArray()
{
return [
'id' => $this->id,
'name' => $this->name,
'totalJobs' => $this->totalJobs,
'pendingJobs' => $this->pendingJobs,
'processedJobs' => $this->processedJobs(),
'progress' => $this->progress(),
'failedJobs' => $this->failedJobs,
'options' => $this->options,
'createdAt' => $this->createdAt,
'cancelledAt' => $this->cancelledAt,
'finishedAt' => $this->finishedAt,
];
}
/**
* Get the JSON serializable representation of the object.
*
* @return array
*/
public function jsonSerialize(): array
{
return $this->toArray();
}
/**
* Dynamically access the batch's "options" via properties.
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
return $this->options[$key] ?? null;
}
}
+57
View File
@@ -0,0 +1,57 @@
<?php
namespace Illuminate\Bus;
use Carbon\CarbonImmutable;
use Illuminate\Contracts\Queue\Factory as QueueFactory;
class BatchFactory
{
/**
* The queue factory implementation.
*
* @var \Illuminate\Contracts\Queue\Factory
*/
protected $queue;
/**
* Create a new batch factory instance.
*
* @param \Illuminate\Contracts\Queue\Factory $queue
*/
public function __construct(QueueFactory $queue)
{
$this->queue = $queue;
}
/**
* Create a new batch instance.
*
* @param \Illuminate\Bus\BatchRepository $repository
* @param string $id
* @param string $name
* @param int $totalJobs
* @param int $pendingJobs
* @param int $failedJobs
* @param array $failedJobIds
* @param array $options
* @param \Carbon\CarbonImmutable $createdAt
* @param \Carbon\CarbonImmutable|null $cancelledAt
* @param \Carbon\CarbonImmutable|null $finishedAt
* @return \Illuminate\Bus\Batch
*/
public function make(BatchRepository $repository,
string $id,
string $name,
int $totalJobs,
int $pendingJobs,
int $failedJobs,
array $failedJobIds,
array $options,
CarbonImmutable $createdAt,
?CarbonImmutable $cancelledAt,
?CarbonImmutable $finishedAt)
{
return new Batch($this->queue, $repository, $id, $name, $totalJobs, $pendingJobs, $failedJobs, $failedJobIds, $options, $createdAt, $cancelledAt, $finishedAt);
}
}
+99
View File
@@ -0,0 +1,99 @@
<?php
namespace Illuminate\Bus;
use Closure;
interface BatchRepository
{
/**
* Retrieve a list of batches.
*
* @param int $limit
* @param mixed $before
* @return \Illuminate\Bus\Batch[]
*/
public function get($limit, $before);
/**
* Retrieve information about an existing batch.
*
* @param string $batchId
* @return \Illuminate\Bus\Batch|null
*/
public function find(string $batchId);
/**
* Store a new pending batch.
*
* @param \Illuminate\Bus\PendingBatch $batch
* @return \Illuminate\Bus\Batch
*/
public function store(PendingBatch $batch);
/**
* Increment the total number of jobs within the batch.
*
* @param string $batchId
* @param int $amount
* @return void
*/
public function incrementTotalJobs(string $batchId, int $amount);
/**
* Decrement the total number of pending jobs for the batch.
*
* @param string $batchId
* @param string $jobId
* @return \Illuminate\Bus\UpdatedBatchJobCounts
*/
public function decrementPendingJobs(string $batchId, string $jobId);
/**
* Increment the total number of failed jobs for the batch.
*
* @param string $batchId
* @param string $jobId
* @return \Illuminate\Bus\UpdatedBatchJobCounts
*/
public function incrementFailedJobs(string $batchId, string $jobId);
/**
* Mark the batch that has the given ID as finished.
*
* @param string $batchId
* @return void
*/
public function markAsFinished(string $batchId);
/**
* Cancel the batch that has the given ID.
*
* @param string $batchId
* @return void
*/
public function cancel(string $batchId);
/**
* Delete the batch that has the given ID.
*
* @param string $batchId
* @return void
*/
public function delete(string $batchId);
/**
* Execute the given Closure within a storage specific transaction.
*
* @param \Closure $callback
* @return mixed
*/
public function transaction(Closure $callback);
/**
* Rollback the last database transaction for the connection.
*
* @return void
*/
public function rollBack();
}
+108
View File
@@ -0,0 +1,108 @@
<?php
namespace Illuminate\Bus;
use Carbon\CarbonImmutable;
use Illuminate\Container\Container;
use Illuminate\Support\Str;
use Illuminate\Support\Testing\Fakes\BatchFake;
trait Batchable
{
/**
* The batch ID (if applicable).
*
* @var string
*/
public $batchId;
/**
* The fake batch, if applicable.
*
* @var \Illuminate\Support\Testing\Fakes\BatchFake
*/
private $fakeBatch;
/**
* Get the batch instance for the job, if applicable.
*
* @return \Illuminate\Bus\Batch|null
*/
public function batch()
{
if ($this->fakeBatch) {
return $this->fakeBatch;
}
if ($this->batchId) {
return Container::getInstance()->make(BatchRepository::class)?->find($this->batchId);
}
}
/**
* Determine if the batch is still active and processing.
*
* @return bool
*/
public function batching()
{
$batch = $this->batch();
return $batch && ! $batch->cancelled();
}
/**
* Set the batch ID on the job.
*
* @param string $batchId
* @return $this
*/
public function withBatchId(string $batchId)
{
$this->batchId = $batchId;
return $this;
}
/**
* Indicate that the job should use a fake batch.
*
* @param string $id
* @param string $name
* @param int $totalJobs
* @param int $pendingJobs
* @param int $failedJobs
* @param array $failedJobIds
* @param array $options
* @param \Carbon\CarbonImmutable|null $createdAt
* @param \Carbon\CarbonImmutable|null $cancelledAt
* @param \Carbon\CarbonImmutable|null $finishedAt
* @return array{0: $this, 1: \Illuminate\Support\Testing\Fakes\BatchFake}
*/
public function withFakeBatch(string $id = '',
string $name = '',
int $totalJobs = 0,
int $pendingJobs = 0,
int $failedJobs = 0,
array $failedJobIds = [],
array $options = [],
?CarbonImmutable $createdAt = null,
?CarbonImmutable $cancelledAt = null,
?CarbonImmutable $finishedAt = null)
{
$this->fakeBatch = new BatchFake(
empty($id) ? (string) Str::uuid() : $id,
$name,
$totalJobs,
$pendingJobs,
$failedJobs,
$failedJobIds,
$options,
$createdAt ?? CarbonImmutable::now(),
$cancelledAt,
$finishedAt,
);
return [$this, $this->fakeBatch];
}
}
+105
View File
@@ -0,0 +1,105 @@
<?php
namespace Illuminate\Bus;
use Aws\DynamoDb\DynamoDbClient;
use Illuminate\Contracts\Bus\Dispatcher as DispatcherContract;
use Illuminate\Contracts\Bus\QueueingDispatcher as QueueingDispatcherContract;
use Illuminate\Contracts\Queue\Factory as QueueFactoryContract;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\Arr;
use Illuminate\Support\ServiceProvider;
class BusServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton(Dispatcher::class, function ($app) {
return new Dispatcher($app, function ($connection = null) use ($app) {
return $app[QueueFactoryContract::class]->connection($connection);
});
});
$this->registerBatchServices();
$this->app->alias(
Dispatcher::class, DispatcherContract::class
);
$this->app->alias(
Dispatcher::class, QueueingDispatcherContract::class
);
}
/**
* Register the batch handling services.
*
* @return void
*/
protected function registerBatchServices()
{
$this->app->singleton(BatchRepository::class, function ($app) {
$driver = $app->config->get('queue.batching.driver', 'database');
return $driver === 'dynamodb'
? $app->make(DynamoBatchRepository::class)
: $app->make(DatabaseBatchRepository::class);
});
$this->app->singleton(DatabaseBatchRepository::class, function ($app) {
return new DatabaseBatchRepository(
$app->make(BatchFactory::class),
$app->make('db')->connection($app->config->get('queue.batching.database')),
$app->config->get('queue.batching.table', 'job_batches')
);
});
$this->app->singleton(DynamoBatchRepository::class, function ($app) {
$config = $app->config->get('queue.batching');
$dynamoConfig = [
'region' => $config['region'],
'version' => 'latest',
'endpoint' => $config['endpoint'] ?? null,
];
if (! empty($config['key']) && ! empty($config['secret'])) {
$dynamoConfig['credentials'] = Arr::only($config, ['key', 'secret']);
if (! empty($config['token'])) {
$dynamoConfig['credentials']['token'] = $config['token'];
}
}
return new DynamoBatchRepository(
$app->make(BatchFactory::class),
new DynamoDbClient($dynamoConfig),
$app->config->get('app.name'),
$app->config->get('queue.batching.table', 'job_batches'),
ttl: $app->config->get('queue.batching.ttl', null),
ttlAttribute: $app->config->get('queue.batching.ttl_attribute', 'ttl'),
);
});
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return [
Dispatcher::class,
DispatcherContract::class,
QueueingDispatcherContract::class,
BatchRepository::class,
DatabaseBatchRepository::class,
];
}
}
+141
View File
@@ -0,0 +1,141 @@
<?php
namespace Illuminate\Bus;
use Illuminate\Container\Container;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Collection;
use Throwable;
class ChainedBatch implements ShouldQueue
{
use Batchable, Dispatchable, InteractsWithQueue, Queueable;
/**
* The collection of batched jobs.
*
* @var \Illuminate\Support\Collection
*/
public Collection $jobs;
/**
* The name of the batch.
*
* @var string
*/
public string $name;
/**
* The batch options.
*
* @var array
*/
public array $options;
/**
* Create a new chained batch instance.
*
* @param \Illuminate\Bus\PendingBatch $batch
*/
public function __construct(PendingBatch $batch)
{
$this->jobs = static::prepareNestedBatches($batch->jobs);
$this->name = $batch->name;
$this->options = $batch->options;
}
/**
* Prepare any nested batches within the given collection of jobs.
*
* @param \Illuminate\Support\Collection $jobs
* @return \Illuminate\Support\Collection
*/
public static function prepareNestedBatches(Collection $jobs): Collection
{
return $jobs->map(fn ($job) => match (true) {
is_array($job) => static::prepareNestedBatches(new Collection($job))->all(),
$job instanceof Collection => static::prepareNestedBatches($job),
$job instanceof PendingBatch => new ChainedBatch($job),
default => $job,
});
}
/**
* Handle the job.
*
* @return void
*/
public function handle()
{
$this->attachRemainderOfChainToEndOfBatch(
$this->toPendingBatch()
)->dispatch();
}
/**
* Convert the chained batch instance into a pending batch.
*
* @return \Illuminate\Bus\PendingBatch
*/
public function toPendingBatch()
{
$batch = Container::getInstance()->make(Dispatcher::class)->batch($this->jobs);
$batch->name = $this->name;
$batch->options = $this->options;
if ($this->queue) {
$batch->onQueue($this->queue);
}
if ($this->connection) {
$batch->onConnection($this->connection);
}
foreach ($this->chainCatchCallbacks ?? [] as $callback) {
$batch->catch(function (Batch $batch, ?Throwable $exception) use ($callback) {
if (! $batch->allowsFailures()) {
$callback($exception);
}
});
}
return $batch;
}
/**
* Move the remainder of the chain to a "finally" batch callback.
*
* @param \Illuminate\Bus\PendingBatch $batch
* @return \Illuminate\Bus\PendingBatch
*/
protected function attachRemainderOfChainToEndOfBatch(PendingBatch $batch)
{
if (! empty($this->chained)) {
$next = unserialize(array_shift($this->chained));
$next->chained = $this->chained;
$next->onConnection($next->connection ?: $this->chainConnection);
$next->onQueue($next->queue ?: $this->chainQueue);
$next->chainConnection = $this->chainConnection;
$next->chainQueue = $this->chainQueue;
$next->chainCatchCallbacks = $this->chainCatchCallbacks;
$batch->finally(function (Batch $batch) use ($next) {
if (! $batch->cancelled()) {
Container::getInstance()->make(Dispatcher::class)->dispatch($next);
}
});
$this->chained = [];
}
return $batch;
}
}
+403
View File
@@ -0,0 +1,403 @@
<?php
namespace Illuminate\Bus;
use Carbon\CarbonImmutable;
use Closure;
use DateTimeInterface;
use Illuminate\Database\Connection;
use Illuminate\Database\PostgresConnection;
use Illuminate\Database\Query\Expression;
use Illuminate\Support\Str;
use Throwable;
class DatabaseBatchRepository implements PrunableBatchRepository
{
/**
* The batch factory instance.
*
* @var \Illuminate\Bus\BatchFactory
*/
protected $factory;
/**
* The database connection instance.
*
* @var \Illuminate\Database\Connection
*/
protected $connection;
/**
* The database table to use to store batch information.
*
* @var string
*/
protected $table;
/**
* Create a new batch repository instance.
*
* @param \Illuminate\Bus\BatchFactory $factory
* @param \Illuminate\Database\Connection $connection
* @param string $table
*/
public function __construct(BatchFactory $factory, Connection $connection, string $table)
{
$this->factory = $factory;
$this->connection = $connection;
$this->table = $table;
}
/**
* Retrieve a list of batches.
*
* @param int $limit
* @param mixed $before
* @return \Illuminate\Bus\Batch[]
*/
public function get($limit = 50, $before = null)
{
return $this->connection->table($this->table)
->orderByDesc('id')
->take($limit)
->when($before, fn ($q) => $q->where('id', '<', $before))
->get()
->map(function ($batch) {
return $this->toBatch($batch);
})
->all();
}
/**
* Retrieve information about an existing batch.
*
* @param string $batchId
* @return \Illuminate\Bus\Batch|null
*/
public function find(string $batchId)
{
$batch = $this->connection->table($this->table)
->useWritePdo()
->where('id', $batchId)
->first();
if ($batch) {
return $this->toBatch($batch);
}
}
/**
* Store a new pending batch.
*
* @param \Illuminate\Bus\PendingBatch $batch
* @return \Illuminate\Bus\Batch
*/
public function store(PendingBatch $batch)
{
$id = (string) Str::orderedUuid();
$this->connection->table($this->table)->insert([
'id' => $id,
'name' => $batch->name,
'total_jobs' => 0,
'pending_jobs' => 0,
'failed_jobs' => 0,
'failed_job_ids' => '[]',
'options' => $this->serialize($batch->options),
'created_at' => time(),
'cancelled_at' => null,
'finished_at' => null,
]);
return $this->find($id);
}
/**
* Increment the total number of jobs within the batch.
*
* @param string $batchId
* @param int $amount
* @return void
*/
public function incrementTotalJobs(string $batchId, int $amount)
{
$this->connection->table($this->table)->where('id', $batchId)->update([
'total_jobs' => new Expression('total_jobs + '.$amount),
'pending_jobs' => new Expression('pending_jobs + '.$amount),
'finished_at' => null,
]);
}
/**
* Decrement the total number of pending jobs for the batch.
*
* @param string $batchId
* @param string $jobId
* @return \Illuminate\Bus\UpdatedBatchJobCounts
*/
public function decrementPendingJobs(string $batchId, string $jobId)
{
$values = $this->updateAtomicValues($batchId, function ($batch) use ($jobId) {
return [
'pending_jobs' => $batch->pending_jobs - 1,
'failed_jobs' => $batch->failed_jobs,
'failed_job_ids' => json_encode(array_values(array_diff((array) json_decode($batch->failed_job_ids, true), [$jobId]))),
];
});
return new UpdatedBatchJobCounts(
$values['pending_jobs'],
$values['failed_jobs']
);
}
/**
* Increment the total number of failed jobs for the batch.
*
* @param string $batchId
* @param string $jobId
* @return \Illuminate\Bus\UpdatedBatchJobCounts
*/
public function incrementFailedJobs(string $batchId, string $jobId)
{
$values = $this->updateAtomicValues($batchId, function ($batch) use ($jobId) {
return [
'pending_jobs' => $batch->pending_jobs,
'failed_jobs' => $batch->failed_jobs + 1,
'failed_job_ids' => json_encode(array_values(array_unique(array_merge((array) json_decode($batch->failed_job_ids, true), [$jobId])))),
];
});
return new UpdatedBatchJobCounts(
$values['pending_jobs'],
$values['failed_jobs']
);
}
/**
* Update an atomic value within the batch.
*
* @param string $batchId
* @param \Closure $callback
* @return int|null
*/
protected function updateAtomicValues(string $batchId, Closure $callback)
{
return $this->connection->transaction(function () use ($batchId, $callback) {
$batch = $this->connection->table($this->table)->where('id', $batchId)
->lockForUpdate()
->first();
return is_null($batch) ? [] : tap($callback($batch), function ($values) use ($batchId) {
$this->connection->table($this->table)->where('id', $batchId)->update($values);
});
});
}
/**
* Mark the batch that has the given ID as finished.
*
* @param string $batchId
* @return void
*/
public function markAsFinished(string $batchId)
{
$this->connection->table($this->table)->where('id', $batchId)->update([
'finished_at' => time(),
]);
}
/**
* Cancel the batch that has the given ID.
*
* @param string $batchId
* @return void
*/
public function cancel(string $batchId)
{
$this->connection->table($this->table)->where('id', $batchId)->update([
'cancelled_at' => time(),
'finished_at' => time(),
]);
}
/**
* Delete the batch that has the given ID.
*
* @param string $batchId
* @return void
*/
public function delete(string $batchId)
{
$this->connection->table($this->table)->where('id', $batchId)->delete();
}
/**
* Prune all of the entries older than the given date.
*
* @param \DateTimeInterface $before
* @return int
*/
public function prune(DateTimeInterface $before)
{
$query = $this->connection->table($this->table)
->whereNotNull('finished_at')
->where('finished_at', '<', $before->getTimestamp());
$totalDeleted = 0;
do {
$deleted = $query->take(1000)->delete();
$totalDeleted += $deleted;
} while ($deleted !== 0);
return $totalDeleted;
}
/**
* Prune all of the unfinished entries older than the given date.
*
* @param \DateTimeInterface $before
* @return int
*/
public function pruneUnfinished(DateTimeInterface $before)
{
$query = $this->connection->table($this->table)
->whereNull('finished_at')
->where('created_at', '<', $before->getTimestamp());
$totalDeleted = 0;
do {
$deleted = $query->take(1000)->delete();
$totalDeleted += $deleted;
} while ($deleted !== 0);
return $totalDeleted;
}
/**
* Prune all of the cancelled entries older than the given date.
*
* @param \DateTimeInterface $before
* @return int
*/
public function pruneCancelled(DateTimeInterface $before)
{
$query = $this->connection->table($this->table)
->whereNotNull('cancelled_at')
->where('created_at', '<', $before->getTimestamp());
$totalDeleted = 0;
do {
$deleted = $query->take(1000)->delete();
$totalDeleted += $deleted;
} while ($deleted !== 0);
return $totalDeleted;
}
/**
* Execute the given Closure within a storage specific transaction.
*
* @param \Closure $callback
* @return mixed
*/
public function transaction(Closure $callback)
{
return $this->connection->transaction(fn () => $callback());
}
/**
* Rollback the last database transaction for the connection.
*
* @return void
*/
public function rollBack()
{
$this->connection->rollBack(toLevel: 0);
}
/**
* Serialize the given value.
*
* @param mixed $value
* @return string
*/
protected function serialize($value)
{
$serialized = serialize($value);
return $this->connection instanceof PostgresConnection
? base64_encode($serialized)
: $serialized;
}
/**
* Unserialize the given value.
*
* @param string $serialized
* @return mixed
*/
protected function unserialize($serialized)
{
if ($this->connection instanceof PostgresConnection &&
! Str::contains($serialized, [':', ';'])) {
$serialized = base64_decode($serialized);
}
try {
return unserialize($serialized);
} catch (Throwable) {
return [];
}
}
/**
* Convert the given raw batch to a Batch object.
*
* @param object $batch
* @return \Illuminate\Bus\Batch
*/
protected function toBatch($batch)
{
return $this->factory->make(
$this,
$batch->id,
$batch->name,
(int) $batch->total_jobs,
(int) $batch->pending_jobs,
(int) $batch->failed_jobs,
(array) json_decode($batch->failed_job_ids, true),
$this->unserialize($batch->options),
CarbonImmutable::createFromTimestamp($batch->created_at, date_default_timezone_get()),
$batch->cancelled_at ? CarbonImmutable::createFromTimestamp($batch->cancelled_at, date_default_timezone_get()) : $batch->cancelled_at,
$batch->finished_at ? CarbonImmutable::createFromTimestamp($batch->finished_at, date_default_timezone_get()) : $batch->finished_at
);
}
/**
* Get the underlying database connection.
*
* @return \Illuminate\Database\Connection
*/
public function getConnection()
{
return $this->connection;
}
/**
* Set the underlying database connection.
*
* @param \Illuminate\Database\Connection $connection
* @return void
*/
public function setConnection(Connection $connection)
{
$this->connection = $connection;
}
}
+322
View File
@@ -0,0 +1,322 @@
<?php
namespace Illuminate\Bus;
use Closure;
use Illuminate\Contracts\Bus\QueueingDispatcher;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Queue\Queue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\PendingChain;
use Illuminate\Pipeline\Pipeline;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Jobs\SyncJob;
use Illuminate\Support\Collection;
use RuntimeException;
class Dispatcher implements QueueingDispatcher
{
/**
* The container implementation.
*
* @var \Illuminate\Contracts\Container\Container
*/
protected $container;
/**
* The pipeline instance for the bus.
*
* @var \Illuminate\Pipeline\Pipeline
*/
protected $pipeline;
/**
* The pipes to send commands through before dispatching.
*
* @var array
*/
protected $pipes = [];
/**
* The command to handler mapping for non-self-handling events.
*
* @var array
*/
protected $handlers = [];
/**
* The queue resolver callback.
*
* @var \Closure|null
*/
protected $queueResolver;
/**
* Indicates if dispatching after response is disabled.
*
* @var bool
*/
protected $allowsDispatchingAfterResponses = true;
/**
* Create a new command dispatcher instance.
*
* @param \Illuminate\Contracts\Container\Container $container
* @param \Closure|null $queueResolver
*/
public function __construct(Container $container, ?Closure $queueResolver = null)
{
$this->container = $container;
$this->queueResolver = $queueResolver;
$this->pipeline = new Pipeline($container);
}
/**
* Dispatch a command to its appropriate handler.
*
* @param mixed $command
* @return mixed
*/
public function dispatch($command)
{
return $this->queueResolver && $this->commandShouldBeQueued($command)
? $this->dispatchToQueue($command)
: $this->dispatchNow($command);
}
/**
* Dispatch a command to its appropriate handler in the current process.
*
* Queueable jobs will be dispatched to the "sync" queue.
*
* @param mixed $command
* @param mixed $handler
* @return mixed
*/
public function dispatchSync($command, $handler = null)
{
if ($this->queueResolver &&
$this->commandShouldBeQueued($command) &&
method_exists($command, 'onConnection')) {
return $this->dispatchToQueue($command->onConnection('sync'));
}
return $this->dispatchNow($command, $handler);
}
/**
* Dispatch a command to its appropriate handler in the current process without using the synchronous queue.
*
* @param mixed $command
* @param mixed $handler
* @return mixed
*/
public function dispatchNow($command, $handler = null)
{
$uses = class_uses_recursive($command);
if (isset($uses[InteractsWithQueue::class], $uses[Queueable::class]) && ! $command->job) {
$command->setJob(new SyncJob($this->container, json_encode([]), 'sync', 'sync'));
}
if ($handler || $handler = $this->getCommandHandler($command)) {
$callback = function ($command) use ($handler) {
$method = method_exists($handler, 'handle') ? 'handle' : '__invoke';
return $handler->{$method}($command);
};
} else {
$callback = function ($command) {
$method = method_exists($command, 'handle') ? 'handle' : '__invoke';
return $this->container->call([$command, $method]);
};
}
return $this->pipeline->send($command)->through($this->pipes)->then($callback);
}
/**
* Attempt to find the batch with the given ID.
*
* @param string $batchId
* @return \Illuminate\Bus\Batch|null
*/
public function findBatch(string $batchId)
{
return $this->container->make(BatchRepository::class)->find($batchId);
}
/**
* Create a new batch of queueable jobs.
*
* @param \Illuminate\Support\Collection|array|mixed $jobs
* @return \Illuminate\Bus\PendingBatch
*/
public function batch($jobs)
{
return new PendingBatch($this->container, Collection::wrap($jobs));
}
/**
* Create a new chain of queueable jobs.
*
* @param \Illuminate\Support\Collection|array $jobs
* @return \Illuminate\Foundation\Bus\PendingChain
*/
public function chain($jobs)
{
$jobs = Collection::wrap($jobs);
$jobs = ChainedBatch::prepareNestedBatches($jobs);
return new PendingChain($jobs->shift(), $jobs->toArray());
}
/**
* Determine if the given command has a handler.
*
* @param mixed $command
* @return bool
*/
public function hasCommandHandler($command)
{
return array_key_exists(get_class($command), $this->handlers);
}
/**
* Retrieve the handler for a command.
*
* @param mixed $command
* @return bool|mixed
*/
public function getCommandHandler($command)
{
if ($this->hasCommandHandler($command)) {
return $this->container->make($this->handlers[get_class($command)]);
}
return false;
}
/**
* Determine if the given command should be queued.
*
* @param mixed $command
* @return bool
*/
protected function commandShouldBeQueued($command)
{
return $command instanceof ShouldQueue;
}
/**
* Dispatch a command to its appropriate handler behind a queue.
*
* @param mixed $command
* @return mixed
*
* @throws \RuntimeException
*/
public function dispatchToQueue($command)
{
$connection = $command->connection ?? null;
$queue = ($this->queueResolver)($connection);
if (! $queue instanceof Queue) {
throw new RuntimeException('Queue resolver did not return a Queue implementation.');
}
if (method_exists($command, 'queue')) {
return $command->queue($queue, $command);
}
return $this->pushCommandToQueue($queue, $command);
}
/**
* Push the command onto the given queue instance.
*
* @param \Illuminate\Contracts\Queue\Queue $queue
* @param mixed $command
* @return mixed
*/
protected function pushCommandToQueue($queue, $command)
{
if (isset($command->delay)) {
return $queue->later($command->delay, $command, queue: $command->queue ?? null);
}
return $queue->push($command, queue: $command->queue ?? null);
}
/**
* Dispatch a command to its appropriate handler after the current process.
*
* @param mixed $command
* @param mixed $handler
* @return void
*/
public function dispatchAfterResponse($command, $handler = null)
{
if (! $this->allowsDispatchingAfterResponses) {
$this->dispatchSync($command);
return;
}
$this->container->terminating(function () use ($command, $handler) {
$this->dispatchSync($command, $handler);
});
}
/**
* Set the pipes through which commands should be piped before dispatching.
*
* @param array $pipes
* @return $this
*/
public function pipeThrough(array $pipes)
{
$this->pipes = $pipes;
return $this;
}
/**
* Map a command to a handler.
*
* @param array $map
* @return $this
*/
public function map(array $map)
{
$this->handlers = array_merge($this->handlers, $map);
return $this;
}
/**
* Allow dispatching after responses.
*
* @return $this
*/
public function withDispatchingAfterResponses()
{
$this->allowsDispatchingAfterResponses = true;
return $this;
}
/**
* Disable dispatching after responses.
*
* @return $this
*/
public function withoutDispatchingAfterResponses()
{
$this->allowsDispatchingAfterResponses = false;
return $this;
}
}
+536
View File
@@ -0,0 +1,536 @@
<?php
namespace Illuminate\Bus;
use Aws\DynamoDb\DynamoDbClient;
use Aws\DynamoDb\Marshaler;
use Carbon\CarbonImmutable;
use Closure;
use Illuminate\Support\Str;
class DynamoBatchRepository implements BatchRepository
{
/**
* The batch factory instance.
*
* @var \Illuminate\Bus\BatchFactory
*/
protected $factory;
/**
* The database connection instance.
*
* @var \Aws\DynamoDb\DynamoDbClient
*/
protected $dynamoDbClient;
/**
* The application name.
*
* @var string
*/
protected $applicationName;
/**
* The table to use to store batch information.
*
* @var string
*/
protected $table;
/**
* The time-to-live value for batch records.
*
* @var int
*/
protected $ttl;
/**
* The name of the time-to-live attribute for batch records.
*
* @var string
*/
protected $ttlAttribute;
/**
* The DynamoDB marshaler instance.
*
* @var \Aws\DynamoDb\Marshaler
*/
protected $marshaler;
/**
* Create a new batch repository instance.
*/
public function __construct(
BatchFactory $factory,
DynamoDbClient $dynamoDbClient,
string $applicationName,
string $table,
?int $ttl,
?string $ttlAttribute,
) {
$this->factory = $factory;
$this->dynamoDbClient = $dynamoDbClient;
$this->applicationName = $applicationName;
$this->table = $table;
$this->ttl = $ttl;
$this->ttlAttribute = $ttlAttribute;
$this->marshaler = new Marshaler;
}
/**
* Retrieve a list of batches.
*
* @param int $limit
* @param mixed $before
* @return \Illuminate\Bus\Batch[]
*/
public function get($limit = 50, $before = null)
{
$condition = 'application = :application';
if ($before) {
$condition = 'application = :application AND id < :id';
}
$result = $this->dynamoDbClient->query([
'TableName' => $this->table,
'KeyConditionExpression' => $condition,
'ExpressionAttributeValues' => array_filter([
':application' => ['S' => $this->applicationName],
':id' => array_filter(['S' => $before]),
]),
'Limit' => $limit,
'ScanIndexForward' => false,
]);
return array_map(
fn ($b) => $this->toBatch($this->marshaler->unmarshalItem($b, mapAsObject: true)),
$result['Items']
);
}
/**
* Retrieve information about an existing batch.
*
* @param string $batchId
* @return \Illuminate\Bus\Batch|null
*/
public function find(string $batchId)
{
if ($batchId === '') {
return null;
}
$b = $this->dynamoDbClient->getItem([
'TableName' => $this->table,
'Key' => [
'application' => ['S' => $this->applicationName],
'id' => ['S' => $batchId],
],
]);
if (! isset($b['Item'])) {
// If we didn't find it via a standard read, attempt consistent read...
$b = $this->dynamoDbClient->getItem([
'TableName' => $this->table,
'Key' => [
'application' => ['S' => $this->applicationName],
'id' => ['S' => $batchId],
],
'ConsistentRead' => true,
]);
if (! isset($b['Item'])) {
return null;
}
}
$batch = $this->marshaler->unmarshalItem($b['Item'], mapAsObject: true);
if ($batch) {
return $this->toBatch($batch);
}
}
/**
* Store a new pending batch.
*
* @param \Illuminate\Bus\PendingBatch $batch
* @return \Illuminate\Bus\Batch
*/
public function store(PendingBatch $batch)
{
$id = (string) Str::orderedUuid();
$batch = [
'id' => $id,
'name' => $batch->name,
'total_jobs' => 0,
'pending_jobs' => 0,
'failed_jobs' => 0,
'failed_job_ids' => [],
'options' => $this->serialize($batch->options ?? []),
'created_at' => time(),
'cancelled_at' => null,
'finished_at' => null,
];
if (! is_null($this->ttl)) {
$batch[$this->ttlAttribute] = time() + $this->ttl;
}
$this->dynamoDbClient->putItem([
'TableName' => $this->table,
'Item' => $this->marshaler->marshalItem(
array_merge(['application' => $this->applicationName], $batch)
),
]);
return $this->find($id);
}
/**
* Increment the total number of jobs within the batch.
*
* @param string $batchId
* @param int $amount
* @return void
*/
public function incrementTotalJobs(string $batchId, int $amount)
{
$update = 'SET total_jobs = total_jobs + :val, pending_jobs = pending_jobs + :val';
if ($this->ttl) {
$update = "SET total_jobs = total_jobs + :val, pending_jobs = pending_jobs + :val, #{$this->ttlAttribute} = :ttl";
}
$this->dynamoDbClient->updateItem(array_filter([
'TableName' => $this->table,
'Key' => [
'application' => ['S' => $this->applicationName],
'id' => ['S' => $batchId],
],
'UpdateExpression' => $update,
'ExpressionAttributeValues' => array_filter([
':val' => ['N' => "$amount"],
':ttl' => array_filter(['N' => $this->getExpiryTime()]),
]),
'ExpressionAttributeNames' => $this->ttlExpressionAttributeName(),
'ReturnValues' => 'ALL_NEW',
]));
}
/**
* Decrement the total number of pending jobs for the batch.
*
* @param string $batchId
* @param string $jobId
* @return \Illuminate\Bus\UpdatedBatchJobCounts
*/
public function decrementPendingJobs(string $batchId, string $jobId)
{
$update = 'SET pending_jobs = pending_jobs - :inc';
if ($this->ttl !== null) {
$update = "SET pending_jobs = pending_jobs - :inc, #{$this->ttlAttribute} = :ttl";
}
$batch = $this->dynamoDbClient->updateItem(array_filter([
'TableName' => $this->table,
'Key' => [
'application' => ['S' => $this->applicationName],
'id' => ['S' => $batchId],
],
'UpdateExpression' => $update,
'ExpressionAttributeValues' => array_filter([
':inc' => ['N' => '1'],
':ttl' => array_filter(['N' => $this->getExpiryTime()]),
]),
'ExpressionAttributeNames' => $this->ttlExpressionAttributeName(),
'ReturnValues' => 'ALL_NEW',
]));
$values = $this->marshaler->unmarshalItem($batch['Attributes']);
return new UpdatedBatchJobCounts(
$values['pending_jobs'],
$values['failed_jobs']
);
}
/**
* Increment the total number of failed jobs for the batch.
*
* @param string $batchId
* @param string $jobId
* @return \Illuminate\Bus\UpdatedBatchJobCounts
*/
public function incrementFailedJobs(string $batchId, string $jobId)
{
$update = 'SET failed_jobs = failed_jobs + :inc, failed_job_ids = list_append(failed_job_ids, :jobId)';
if ($this->ttl !== null) {
$update = "SET failed_jobs = failed_jobs + :inc, failed_job_ids = list_append(failed_job_ids, :jobId), #{$this->ttlAttribute} = :ttl";
}
$batch = $this->dynamoDbClient->updateItem(array_filter([
'TableName' => $this->table,
'Key' => [
'application' => ['S' => $this->applicationName],
'id' => ['S' => $batchId],
],
'UpdateExpression' => $update,
'ExpressionAttributeValues' => array_filter([
':jobId' => $this->marshaler->marshalValue([$jobId]),
':inc' => ['N' => '1'],
':ttl' => array_filter(['N' => $this->getExpiryTime()]),
]),
'ExpressionAttributeNames' => $this->ttlExpressionAttributeName(),
'ReturnValues' => 'ALL_NEW',
]));
$values = $this->marshaler->unmarshalItem($batch['Attributes']);
return new UpdatedBatchJobCounts(
$values['pending_jobs'],
$values['failed_jobs']
);
}
/**
* Mark the batch that has the given ID as finished.
*
* @param string $batchId
* @return void
*/
public function markAsFinished(string $batchId)
{
$update = 'SET finished_at = :timestamp';
if ($this->ttl !== null) {
$update = "SET finished_at = :timestamp, #{$this->ttlAttribute} = :ttl";
}
$this->dynamoDbClient->updateItem(array_filter([
'TableName' => $this->table,
'Key' => [
'application' => ['S' => $this->applicationName],
'id' => ['S' => $batchId],
],
'UpdateExpression' => $update,
'ExpressionAttributeValues' => array_filter([
':timestamp' => ['N' => (string) time()],
':ttl' => array_filter(['N' => $this->getExpiryTime()]),
]),
'ExpressionAttributeNames' => $this->ttlExpressionAttributeName(),
]));
}
/**
* Cancel the batch that has the given ID.
*
* @param string $batchId
* @return void
*/
public function cancel(string $batchId)
{
$update = 'SET cancelled_at = :timestamp, finished_at = :timestamp';
if ($this->ttl !== null) {
$update = "SET cancelled_at = :timestamp, finished_at = :timestamp, #{$this->ttlAttribute} = :ttl";
}
$this->dynamoDbClient->updateItem(array_filter([
'TableName' => $this->table,
'Key' => [
'application' => ['S' => $this->applicationName],
'id' => ['S' => $batchId],
],
'UpdateExpression' => $update,
'ExpressionAttributeValues' => array_filter([
':timestamp' => ['N' => (string) time()],
':ttl' => array_filter(['N' => $this->getExpiryTime()]),
]),
'ExpressionAttributeNames' => $this->ttlExpressionAttributeName(),
]));
}
/**
* Delete the batch that has the given ID.
*
* @param string $batchId
* @return void
*/
public function delete(string $batchId)
{
$this->dynamoDbClient->deleteItem([
'TableName' => $this->table,
'Key' => [
'application' => ['S' => $this->applicationName],
'id' => ['S' => $batchId],
],
]);
}
/**
* Execute the given Closure within a storage specific transaction.
*
* @param \Closure $callback
* @return mixed
*/
public function transaction(Closure $callback)
{
return $callback();
}
/**
* Rollback the last database transaction for the connection.
*
* @return void
*/
public function rollBack()
{
}
/**
* Convert the given raw batch to a Batch object.
*
* @param object $batch
* @return \Illuminate\Bus\Batch
*/
protected function toBatch($batch)
{
return $this->factory->make(
$this,
$batch->id,
$batch->name,
(int) $batch->total_jobs,
(int) $batch->pending_jobs,
(int) $batch->failed_jobs,
$batch->failed_job_ids,
$this->unserialize($batch->options) ?? [],
CarbonImmutable::createFromTimestamp($batch->created_at, date_default_timezone_get()),
$batch->cancelled_at ? CarbonImmutable::createFromTimestamp($batch->cancelled_at, date_default_timezone_get()) : $batch->cancelled_at,
$batch->finished_at ? CarbonImmutable::createFromTimestamp($batch->finished_at, date_default_timezone_get()) : $batch->finished_at
);
}
/**
* Create the underlying DynamoDB table.
*
* @return void
*/
public function createAwsDynamoTable(): void
{
$definition = [
'TableName' => $this->table,
'AttributeDefinitions' => [
[
'AttributeName' => 'application',
'AttributeType' => 'S',
],
[
'AttributeName' => 'id',
'AttributeType' => 'S',
],
],
'KeySchema' => [
[
'AttributeName' => 'application',
'KeyType' => 'HASH',
],
[
'AttributeName' => 'id',
'KeyType' => 'RANGE',
],
],
'BillingMode' => 'PAY_PER_REQUEST',
];
$this->dynamoDbClient->createTable($definition);
if (! is_null($this->ttl)) {
$this->dynamoDbClient->updateTimeToLive([
'TableName' => $this->table,
'TimeToLiveSpecification' => [
'AttributeName' => $this->ttlAttribute,
'Enabled' => true,
],
]);
}
}
/**
* Delete the underlying DynamoDB table.
*/
public function deleteAwsDynamoTable(): void
{
$this->dynamoDbClient->deleteTable([
'TableName' => $this->table,
]);
}
/**
* Get the expiry time based on the configured time-to-live.
*
* @return string|null
*/
protected function getExpiryTime(): ?string
{
return is_null($this->ttl) ? null : (string) (time() + $this->ttl);
}
/**
* Get the expression attribute name for the time-to-live attribute.
*
* @return array
*/
protected function ttlExpressionAttributeName(): array
{
return is_null($this->ttl) ? [] : ["#{$this->ttlAttribute}" => $this->ttlAttribute];
}
/**
* Serialize the given value.
*
* @param mixed $value
* @return string
*/
protected function serialize($value)
{
return serialize($value);
}
/**
* Unserialize the given value.
*
* @param string $serialized
* @return mixed
*/
protected function unserialize($serialized)
{
return unserialize($serialized);
}
/**
* Get the underlying DynamoDB client instance.
*
* @return \Aws\DynamoDb\DynamoDbClient
*/
public function getDynamoClient(): DynamoDbClient
{
return $this->dynamoDbClient;
}
/**
* The name of the table that contains the batch records.
*
* @return string
*/
public function getTable(): string
{
return $this->table;
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace Illuminate\Bus\Events;
use Illuminate\Bus\Batch;
class BatchDispatched
{
/**
* Create a new event instance.
*
* @param \Illuminate\Bus\Batch $batch The batch instance.
*/
public function __construct(
public Batch $batch,
) {
}
}
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Taylor Otwell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+452
View File
@@ -0,0 +1,452 @@
<?php
namespace Illuminate\Bus;
use Closure;
use Illuminate\Bus\Events\BatchDispatched;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Events\Dispatcher as EventDispatcher;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Traits\Conditionable;
use Laravel\SerializableClosure\SerializableClosure;
use RuntimeException;
use Throwable;
use function Illuminate\Support\enum_value;
class PendingBatch
{
use Conditionable;
/**
* The IoC container instance.
*
* @var \Illuminate\Contracts\Container\Container
*/
protected $container;
/**
* The batch name.
*
* @var string
*/
public $name = '';
/**
* The jobs that belong to the batch.
*
* @var \Illuminate\Support\Collection
*/
public $jobs;
/**
* The batch options.
*
* @var array
*/
public $options = [];
/**
* Jobs that have been verified to contain the Batchable trait.
*
* @var array<class-string, bool>
*/
protected static $batchableClasses = [];
/**
* Create a new pending batch instance.
*
* @param \Illuminate\Contracts\Container\Container $container
* @param \Illuminate\Support\Collection $jobs
*/
public function __construct(Container $container, Collection $jobs)
{
$this->container = $container;
$this->jobs = $jobs->each(function (object|array $job) {
$this->ensureJobIsBatchable($job);
});
}
/**
* Add jobs to the batch.
*
* @param iterable|object|array $jobs
* @return $this
*/
public function add($jobs)
{
$jobs = is_iterable($jobs) ? $jobs : Arr::wrap($jobs);
foreach ($jobs as $job) {
$this->ensureJobIsBatchable($job);
$this->jobs->push($job);
}
return $this;
}
/**
* Ensure the given job is batchable.
*
* @param object|array $job
* @return void
*/
protected function ensureJobIsBatchable(object|array $job): void
{
foreach (Arr::wrap($job) as $job) {
if ($job instanceof PendingBatch || $job instanceof Closure) {
return;
}
if (! (static::$batchableClasses[$job::class] ?? false) && ! in_array(Batchable::class, class_uses_recursive($job))) {
static::$batchableClasses[$job::class] = false;
throw new RuntimeException(sprintf('Attempted to batch job [%s], but it does not use the Batchable trait.', $job::class));
}
static::$batchableClasses[$job::class] = true;
}
}
/**
* Add a callback to be executed when the batch is stored.
*
* @param callable $callback
* @return $this
*/
public function before($callback)
{
$this->options['before'][] = $callback instanceof Closure
? new SerializableClosure($callback)
: $callback;
return $this;
}
/**
* Get the "before" callbacks that have been registered with the pending batch.
*
* @return array
*/
public function beforeCallbacks()
{
return $this->options['before'] ?? [];
}
/**
* Add a callback to be executed after a job in the batch have executed successfully.
*
* @param callable $callback
* @return $this
*/
public function progress($callback)
{
$this->options['progress'][] = $callback instanceof Closure
? new SerializableClosure($callback)
: $callback;
return $this;
}
/**
* Get the "progress" callbacks that have been registered with the pending batch.
*
* @return array
*/
public function progressCallbacks()
{
return $this->options['progress'] ?? [];
}
/**
* Add a callback to be executed after all jobs in the batch have executed successfully.
*
* @param callable $callback
* @return $this
*/
public function then($callback)
{
$this->options['then'][] = $callback instanceof Closure
? new SerializableClosure($callback)
: $callback;
return $this;
}
/**
* Get the "then" callbacks that have been registered with the pending batch.
*
* @return array
*/
public function thenCallbacks()
{
return $this->options['then'] ?? [];
}
/**
* Add a callback to be executed after the first failing job in the batch.
*
* @param callable $callback
* @return $this
*/
public function catch($callback)
{
$this->options['catch'][] = $callback instanceof Closure
? new SerializableClosure($callback)
: $callback;
return $this;
}
/**
* Get the "catch" callbacks that have been registered with the pending batch.
*
* @return array
*/
public function catchCallbacks()
{
return $this->options['catch'] ?? [];
}
/**
* Add a callback to be executed after the batch has finished executing.
*
* @param callable $callback
* @return $this
*/
public function finally($callback)
{
$this->options['finally'][] = $callback instanceof Closure
? new SerializableClosure($callback)
: $callback;
return $this;
}
/**
* Get the "finally" callbacks that have been registered with the pending batch.
*
* @return array
*/
public function finallyCallbacks()
{
return $this->options['finally'] ?? [];
}
/**
* Indicate that the batch should not be cancelled when a job within the batch fails.
*
* @param bool $allowFailures
* @return $this
*/
public function allowFailures($allowFailures = true)
{
$this->options['allowFailures'] = $allowFailures;
return $this;
}
/**
* Determine if the pending batch allows jobs to fail without cancelling the batch.
*
* @return bool
*/
public function allowsFailures()
{
return Arr::get($this->options, 'allowFailures', false) === true;
}
/**
* Set the name for the batch.
*
* @param string $name
* @return $this
*/
public function name(string $name)
{
$this->name = $name;
return $this;
}
/**
* Specify the queue connection that the batched jobs should run on.
*
* @param string $connection
* @return $this
*/
public function onConnection(string $connection)
{
$this->options['connection'] = $connection;
return $this;
}
/**
* Get the connection used by the pending batch.
*
* @return string|null
*/
public function connection()
{
return $this->options['connection'] ?? null;
}
/**
* Specify the queue that the batched jobs should run on.
*
* @param \UnitEnum|string|null $queue
* @return $this
*/
public function onQueue($queue)
{
$this->options['queue'] = enum_value($queue);
return $this;
}
/**
* Get the queue used by the pending batch.
*
* @return string|null
*/
public function queue()
{
return $this->options['queue'] ?? null;
}
/**
* Add additional data into the batch's options array.
*
* @param string $key
* @param mixed $value
* @return $this
*/
public function withOption(string $key, $value)
{
$this->options[$key] = $value;
return $this;
}
/**
* Dispatch the batch.
*
* @return \Illuminate\Bus\Batch
*
* @throws \Throwable
*/
public function dispatch()
{
$repository = $this->container->make(BatchRepository::class);
try {
$batch = $this->store($repository);
$batch = $batch->add($this->jobs);
} catch (Throwable $e) {
if (isset($batch)) {
$repository->delete($batch->id);
}
throw $e;
}
$this->container->make(EventDispatcher::class)->dispatch(
new BatchDispatched($batch)
);
return $batch;
}
/**
* Dispatch the batch after the response is sent to the browser.
*
* @return \Illuminate\Bus\Batch
*/
public function dispatchAfterResponse()
{
$repository = $this->container->make(BatchRepository::class);
$batch = $this->store($repository);
if ($batch) {
$this->container->terminating(function () use ($batch) {
$this->dispatchExistingBatch($batch);
});
}
return $batch;
}
/**
* Dispatch an existing batch.
*
* @param \Illuminate\Bus\Batch $batch
* @return void
*
* @throws \Throwable
*/
protected function dispatchExistingBatch($batch)
{
try {
$batch = $batch->add($this->jobs);
} catch (Throwable $e) {
$batch->delete();
throw $e;
}
$this->container->make(EventDispatcher::class)->dispatch(
new BatchDispatched($batch)
);
}
/**
* Dispatch the batch if the given truth test passes.
*
* @param bool|\Closure $boolean
* @return \Illuminate\Bus\Batch|null
*/
public function dispatchIf($boolean)
{
return value($boolean) ? $this->dispatch() : null;
}
/**
* Dispatch the batch unless the given truth test passes.
*
* @param bool|\Closure $boolean
* @return \Illuminate\Bus\Batch|null
*/
public function dispatchUnless($boolean)
{
return ! value($boolean) ? $this->dispatch() : null;
}
/**
* Store the batch using the given repository.
*
* @param \Illuminate\Bus\BatchRepository $repository
* @return \Illuminate\Bus\Batch
*/
protected function store($repository)
{
$batch = $repository->store($this);
(new Collection($this->beforeCallbacks()))->each(function ($handler) use ($batch) {
try {
return $handler($batch);
} catch (Throwable $e) {
if (function_exists('report')) {
report($e);
}
}
});
return $batch;
}
}
+16
View File
@@ -0,0 +1,16 @@
<?php
namespace Illuminate\Bus;
use DateTimeInterface;
interface PrunableBatchRepository extends BatchRepository
{
/**
* Prune all of the entries older than the given date.
*
* @param \DateTimeInterface $before
* @return int
*/
public function prune(DateTimeInterface $before);
}
+341
View File
@@ -0,0 +1,341 @@
<?php
namespace Illuminate\Bus;
use Closure;
use Illuminate\Queue\CallQueuedClosure;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use PHPUnit\Framework\Assert as PHPUnit;
use RuntimeException;
use function Illuminate\Support\enum_value;
trait Queueable
{
/**
* The name of the connection the job should be sent to.
*
* @var string|null
*/
public $connection;
/**
* The name of the queue the job should be sent to.
*
* @var string|null
*/
public $queue;
/**
* The number of seconds before the job should be made available.
*
* @var \DateTimeInterface|\DateInterval|array|int|null
*/
public $delay;
/**
* Indicates whether the job should be dispatched after all database transactions have committed.
*
* @var bool|null
*/
public $afterCommit;
/**
* The middleware the job should be dispatched through.
*
* @var array
*/
public $middleware = [];
/**
* The jobs that should run if this job is successful.
*
* @var array
*/
public $chained = [];
/**
* The name of the connection the chain should be sent to.
*
* @var string|null
*/
public $chainConnection;
/**
* The name of the queue the chain should be sent to.
*
* @var string|null
*/
public $chainQueue;
/**
* The callbacks to be executed on chain failure.
*
* @var array|null
*/
public $chainCatchCallbacks;
/**
* Set the desired connection for the job.
*
* @param \UnitEnum|string|null $connection
* @return $this
*/
public function onConnection($connection)
{
$this->connection = enum_value($connection);
return $this;
}
/**
* Set the desired queue for the job.
*
* @param \UnitEnum|string|null $queue
* @return $this
*/
public function onQueue($queue)
{
$this->queue = enum_value($queue);
return $this;
}
/**
* Set the desired connection for the chain.
*
* @param \UnitEnum|string|null $connection
* @return $this
*/
public function allOnConnection($connection)
{
$resolvedConnection = enum_value($connection);
$this->chainConnection = $resolvedConnection;
$this->connection = $resolvedConnection;
return $this;
}
/**
* Set the desired queue for the chain.
*
* @param \UnitEnum|string|null $queue
* @return $this
*/
public function allOnQueue($queue)
{
$resolvedQueue = enum_value($queue);
$this->chainQueue = $resolvedQueue;
$this->queue = $resolvedQueue;
return $this;
}
/**
* Set the desired delay in seconds for the job.
*
* @param \DateTimeInterface|\DateInterval|array|int|null $delay
* @return $this
*/
public function delay($delay)
{
$this->delay = $delay;
return $this;
}
/**
* Set the delay for the job to zero seconds.
*
* @return $this
*/
public function withoutDelay()
{
$this->delay = 0;
return $this;
}
/**
* Indicate that the job should be dispatched after all database transactions have committed.
*
* @return $this
*/
public function afterCommit()
{
$this->afterCommit = true;
return $this;
}
/**
* Indicate that the job should not wait until database transactions have been committed before dispatching.
*
* @return $this
*/
public function beforeCommit()
{
$this->afterCommit = false;
return $this;
}
/**
* Specify the middleware the job should be dispatched through.
*
* @param array|object $middleware
* @return $this
*/
public function through($middleware)
{
$this->middleware = Arr::wrap($middleware);
return $this;
}
/**
* Set the jobs that should run if this job is successful.
*
* @param array $chain
* @return $this
*/
public function chain($chain)
{
$jobs = ChainedBatch::prepareNestedBatches(new Collection($chain));
$this->chained = $jobs->map(function ($job) {
return $this->serializeJob($job);
})->all();
return $this;
}
/**
* Prepend a job to the current chain so that it is run after the currently running job.
*
* @param mixed $job
* @return $this
*/
public function prependToChain($job)
{
$jobs = ChainedBatch::prepareNestedBatches(Collection::wrap($job));
foreach ($jobs->reverse() as $job) {
$this->chained = Arr::prepend($this->chained, $this->serializeJob($job));
}
return $this;
}
/**
* Append a job to the end of the current chain.
*
* @param mixed $job
* @return $this
*/
public function appendToChain($job)
{
$jobs = ChainedBatch::prepareNestedBatches(Collection::wrap($job));
foreach ($jobs as $job) {
$this->chained = array_merge($this->chained, [$this->serializeJob($job)]);
}
return $this;
}
/**
* Serialize a job for queuing.
*
* @param mixed $job
* @return string
*
* @throws \RuntimeException
*/
protected function serializeJob($job)
{
if ($job instanceof Closure) {
if (! class_exists(CallQueuedClosure::class)) {
throw new RuntimeException(
'To enable support for closure jobs, please install the illuminate/queue package.'
);
}
$job = CallQueuedClosure::create($job);
}
return serialize($job);
}
/**
* Dispatch the next job on the chain.
*
* @return void
*/
public function dispatchNextJobInChain()
{
if (! empty($this->chained)) {
dispatch(tap(unserialize(array_shift($this->chained)), function ($next) {
$next->chained = $this->chained;
$next->onConnection($next->connection ?: $this->chainConnection);
$next->onQueue($next->queue ?: $this->chainQueue);
$next->chainConnection = $this->chainConnection;
$next->chainQueue = $this->chainQueue;
$next->chainCatchCallbacks = $this->chainCatchCallbacks;
}));
}
}
/**
* Invoke all of the chain's failed job callbacks.
*
* @param \Throwable $e
* @return void
*/
public function invokeChainCatchCallbacks($e)
{
(new Collection($this->chainCatchCallbacks))->each(function ($callback) use ($e) {
$callback($e);
});
}
/**
* Assert that the job has the given chain of jobs attached to it.
*
* @param array $expectedChain
* @return void
*/
public function assertHasChain($expectedChain)
{
PHPUnit::assertTrue(
(new Collection($expectedChain))->isNotEmpty(),
'The expected chain can not be empty.'
);
if ((new Collection($expectedChain))->contains(fn ($job) => is_object($job))) {
$expectedChain = (new Collection($expectedChain))->map(fn ($job) => serialize($job))->all();
} else {
$chain = (new Collection($this->chained))->map(fn ($job) => get_class(unserialize($job)))->all();
}
PHPUnit::assertTrue(
$expectedChain === ($chain ?? $this->chained),
'The job does not have the expected chain.'
);
}
/**
* Assert that the job has no remaining chained jobs.
*
* @return void
*/
public function assertDoesntHaveChain()
{
PHPUnit::assertEmpty($this->chained, 'The job has chained jobs.');
}
}
+74
View File
@@ -0,0 +1,74 @@
<?php
namespace Illuminate\Bus;
use Illuminate\Contracts\Cache\Repository as Cache;
class UniqueLock
{
/**
* The cache repository implementation.
*
* @var \Illuminate\Contracts\Cache\Repository
*/
protected $cache;
/**
* Create a new unique lock manager instance.
*
* @param \Illuminate\Contracts\Cache\Repository $cache
*/
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
/**
* Attempt to acquire a lock for the given job.
*
* @param mixed $job
* @return bool
*/
public function acquire($job)
{
$uniqueFor = method_exists($job, 'uniqueFor')
? $job->uniqueFor()
: ($job->uniqueFor ?? 0);
$cache = method_exists($job, 'uniqueVia')
? $job->uniqueVia()
: $this->cache;
return (bool) $cache->lock($this->getKey($job), $uniqueFor)->get();
}
/**
* Release the lock for the given job.
*
* @param mixed $job
* @return void
*/
public function release($job)
{
$cache = method_exists($job, 'uniqueVia')
? $job->uniqueVia()
: $this->cache;
$cache->lock($this->getKey($job))->forceRelease();
}
/**
* Generate the lock key for the given job.
*
* @param mixed $job
* @return string
*/
public static function getKey($job)
{
$uniqueId = method_exists($job, 'uniqueId')
? $job->uniqueId()
: ($job->uniqueId ?? '');
return 'laravel_unique_job:'.get_class($job).':'.$uniqueId;
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
namespace Illuminate\Bus;
class UpdatedBatchJobCounts
{
/**
* The number of pending jobs remaining for the batch.
*
* @var int
*/
public $pendingJobs;
/**
* The number of failed jobs that belong to the batch.
*
* @var int
*/
public $failedJobs;
/**
* Create a new batch job counts object.
*
* @param int $pendingJobs
* @param int $failedJobs
*/
public function __construct(int $pendingJobs = 0, int $failedJobs = 0)
{
$this->pendingJobs = $pendingJobs;
$this->failedJobs = $failedJobs;
}
/**
* Determine if all jobs have run exactly once.
*
* @return bool
*/
public function allJobsHaveRanExactlyOnce()
{
return ($this->pendingJobs - $this->failedJobs) === 0;
}
}
+40
View File
@@ -0,0 +1,40 @@
{
"name": "illuminate/bus",
"description": "The Illuminate Bus package.",
"license": "MIT",
"homepage": "https://laravel.com",
"support": {
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"require": {
"php": "^8.2",
"illuminate/collections": "^12.0",
"illuminate/contracts": "^12.0",
"illuminate/pipeline": "^12.0",
"illuminate/support": "^12.0"
},
"autoload": {
"psr-4": {
"Illuminate\\Bus\\": ""
}
},
"extra": {
"branch-alias": {
"dev-master": "12.x-dev"
}
},
"suggest": {
"illuminate/queue": "Required to use closures when chaining jobs (^12.0)."
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev"
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,69 @@
<?php
namespace Illuminate\Support;
/**
* @template TKey of array-key
*
* @template-covariant TValue
*
* @mixin \Illuminate\Support\Enumerable<TKey, TValue>
* @mixin TValue
*/
class HigherOrderCollectionProxy
{
/**
* The collection being operated on.
*
* @var \Illuminate\Support\Enumerable<TKey, TValue>
*/
protected $collection;
/**
* The method being proxied.
*
* @var string
*/
protected $method;
/**
* Create a new proxy instance.
*
* @param \Illuminate\Support\Enumerable<TKey, TValue> $collection
* @param string $method
*/
public function __construct(Enumerable $collection, $method)
{
$this->method = $method;
$this->collection = $collection;
}
/**
* Proxy accessing an attribute onto the collection items.
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
return $this->collection->{$this->method}(function ($value) use ($key) {
return is_array($value) ? $value[$key] : $value->{$key};
});
}
/**
* Proxy a method call onto the collection items.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return $this->collection->{$this->method}(function ($value) use ($method, $parameters) {
return is_string($value)
? $value::{$method}(...$parameters)
: $value->{$method}(...$parameters);
});
}
}
@@ -0,0 +1,9 @@
<?php
namespace Illuminate\Support;
use RuntimeException;
class ItemNotFoundException extends RuntimeException
{
}
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Taylor Otwell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,39 @@
<?php
namespace Illuminate\Support;
use RuntimeException;
class MultipleItemsFoundException extends RuntimeException
{
/**
* The number of items found.
*
* @var int
*/
public $count;
/**
* Create a new exception instance.
*
* @param int $count
* @param int $code
* @param \Throwable|null $previous
*/
public function __construct($count, $code = 0, $previous = null)
{
$this->count = $count;
parent::__construct("$count items were found.", $code, $previous);
}
/**
* Get the number of items found.
*
* @return int
*/
public function getCount()
{
return $this->count;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,68 @@
<?php
namespace Illuminate\Support\Traits;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Resources\Json\ResourceCollection;
use LogicException;
trait TransformsToResourceCollection
{
/**
* Create a new resource collection instance for the given resource.
*
* @param class-string<\Illuminate\Http\Resources\Json\JsonResource>|null $resourceClass
* @return \Illuminate\Http\Resources\Json\ResourceCollection
*
* @throws \Throwable
*/
public function toResourceCollection(?string $resourceClass = null): ResourceCollection
{
if ($resourceClass === null) {
return $this->guessResourceCollection();
}
return $resourceClass::collection($this);
}
/**
* Guess the resource collection for the items.
*
* @return \Illuminate\Http\Resources\Json\ResourceCollection
*
* @throws \Throwable
*/
protected function guessResourceCollection(): ResourceCollection
{
if ($this->isEmpty()) {
return new ResourceCollection($this);
}
$model = $this->items[0] ?? null;
throw_unless(is_object($model), LogicException::class, 'Resource collection guesser expects the collection to contain objects.');
/** @var class-string<Model> $className */
$className = get_class($model);
throw_unless(method_exists($className, 'guessResourceName'), LogicException::class, sprintf('Expected class %s to implement guessResourceName method. Make sure the model uses the TransformsToResource trait.', $className));
$resourceClasses = $className::guessResourceName();
foreach ($resourceClasses as $resourceClass) {
$resourceCollection = $resourceClass.'Collection';
if (is_string($resourceCollection) && class_exists($resourceCollection)) {
return new $resourceCollection($this);
}
}
foreach ($resourceClasses as $resourceClass) {
if (is_string($resourceClass) && class_exists($resourceClass)) {
return $resourceClass::collection($this);
}
}
throw new LogicException(sprintf('Failed to find resource class for model [%s].', $className));
}
}
+44
View File
@@ -0,0 +1,44 @@
{
"name": "illuminate/collections",
"description": "The Illuminate Collections package.",
"license": "MIT",
"homepage": "https://laravel.com",
"support": {
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"require": {
"php": "^8.2",
"illuminate/conditionable": "^12.0",
"illuminate/contracts": "^12.0",
"illuminate/macroable": "^12.0"
},
"autoload": {
"psr-4": {
"Illuminate\\Support\\": ""
},
"files": [
"functions.php",
"helpers.php"
]
},
"extra": {
"branch-alias": {
"dev-master": "12.x-dev"
}
},
"suggest": {
"illuminate/http": "Required to convert collections to API resources (^12.0).",
"symfony/var-dumper": "Required to use the dump method (^7.2)."
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev"
}
+27
View File
@@ -0,0 +1,27 @@
<?php
namespace Illuminate\Support;
if (! function_exists('Illuminate\Support\enum_value')) {
/**
* Return a scalar value for the given value that might be an enum.
*
* @internal
*
* @template TValue
* @template TDefault
*
* @param TValue $value
* @param TDefault|callable(TValue): TDefault $default
* @return ($value is empty ? TDefault : mixed)
*/
function enum_value($value, $default = null)
{
return match (true) {
$value instanceof \BackedEnum => $value->value,
$value instanceof \UnitEnum => $value->name,
default => $value ?? value($default),
};
}
}
+259
View File
@@ -0,0 +1,259 @@
<?php
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
if (! function_exists('collect')) {
/**
* Create a collection from the given value.
*
* @template TKey of array-key
* @template TValue
*
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue>|null $value
* @return \Illuminate\Support\Collection<TKey, TValue>
*/
function collect($value = [])
{
return new Collection($value);
}
}
if (! function_exists('data_fill')) {
/**
* Fill in data where it's missing.
*
* @param mixed $target
* @param string|array $key
* @param mixed $value
* @return mixed
*/
function data_fill(&$target, $key, $value)
{
return data_set($target, $key, $value, false);
}
}
if (! function_exists('data_get')) {
/**
* Get an item from an array or object using "dot" notation.
*
* @param mixed $target
* @param string|array|int|null $key
* @param mixed $default
* @return mixed
*/
function data_get($target, $key, $default = null)
{
if (is_null($key)) {
return $target;
}
$key = is_array($key) ? $key : explode('.', $key);
foreach ($key as $i => $segment) {
unset($key[$i]);
if (is_null($segment)) {
return $target;
}
if ($segment === '*') {
if ($target instanceof Collection) {
$target = $target->all();
} elseif (! is_iterable($target)) {
return value($default);
}
$result = [];
foreach ($target as $item) {
$result[] = data_get($item, $key);
}
return in_array('*', $key) ? Arr::collapse($result) : $result;
}
$segment = match ($segment) {
'\*' => '*',
'\{first}' => '{first}',
'{first}' => array_key_first(Arr::from($target)),
'\{last}' => '{last}',
'{last}' => array_key_last(Arr::from($target)),
default => $segment,
};
if (Arr::accessible($target) && Arr::exists($target, $segment)) {
$target = $target[$segment];
} elseif (is_object($target) && isset($target->{$segment})) {
$target = $target->{$segment};
} else {
return value($default);
}
}
return $target;
}
}
if (! function_exists('data_set')) {
/**
* Set an item on an array or object using dot notation.
*
* @param mixed $target
* @param string|array $key
* @param mixed $value
* @param bool $overwrite
* @return mixed
*/
function data_set(&$target, $key, $value, $overwrite = true)
{
$segments = is_array($key) ? $key : explode('.', $key);
if (($segment = array_shift($segments)) === '*') {
if (! Arr::accessible($target)) {
$target = [];
}
if ($segments) {
foreach ($target as &$inner) {
data_set($inner, $segments, $value, $overwrite);
}
} elseif ($overwrite) {
foreach ($target as &$inner) {
$inner = $value;
}
}
} elseif (Arr::accessible($target)) {
if ($segments) {
if (! Arr::exists($target, $segment)) {
$target[$segment] = [];
}
data_set($target[$segment], $segments, $value, $overwrite);
} elseif ($overwrite || ! Arr::exists($target, $segment)) {
$target[$segment] = $value;
}
} elseif (is_object($target)) {
if ($segments) {
if (! isset($target->{$segment})) {
$target->{$segment} = [];
}
data_set($target->{$segment}, $segments, $value, $overwrite);
} elseif ($overwrite || ! isset($target->{$segment})) {
$target->{$segment} = $value;
}
} else {
$target = [];
if ($segments) {
data_set($target[$segment], $segments, $value, $overwrite);
} elseif ($overwrite) {
$target[$segment] = $value;
}
}
return $target;
}
}
if (! function_exists('data_forget')) {
/**
* Remove / unset an item from an array or object using "dot" notation.
*
* @param mixed $target
* @param string|array|int|null $key
* @return mixed
*/
function data_forget(&$target, $key)
{
$segments = is_array($key) ? $key : explode('.', $key);
if (($segment = array_shift($segments)) === '*' && Arr::accessible($target)) {
if ($segments) {
foreach ($target as &$inner) {
data_forget($inner, $segments);
}
}
} elseif (Arr::accessible($target)) {
if ($segments && Arr::exists($target, $segment)) {
data_forget($target[$segment], $segments);
} else {
Arr::forget($target, $segment);
}
} elseif (is_object($target)) {
if ($segments && isset($target->{$segment})) {
data_forget($target->{$segment}, $segments);
} elseif (isset($target->{$segment})) {
unset($target->{$segment});
}
}
return $target;
}
}
if (! function_exists('head')) {
/**
* Get the first element of an array. Useful for method chaining.
*
* @param array $array
* @return mixed
*/
function head($array)
{
return reset($array);
}
}
if (! function_exists('last')) {
/**
* Get the last element from an array.
*
* @param array $array
* @return mixed
*/
function last($array)
{
return end($array);
}
}
if (! function_exists('value')) {
/**
* Return the default value of the given value.
*
* @template TValue
* @template TArgs
*
* @param TValue|\Closure(TArgs): TValue $value
* @param TArgs ...$args
* @return TValue
*/
function value($value, ...$args)
{
return $value instanceof Closure ? $value(...$args) : $value;
}
}
if (! function_exists('when')) {
/**
* Return a value if the given condition is true.
*
* @param mixed $condition
* @param \Closure|mixed $value
* @param \Closure|mixed $default
* @return mixed
*/
function when($condition, $value, $default = null)
{
$condition = $condition instanceof Closure ? $condition() : $condition;
if ($condition) {
return value($value, $condition);
}
return value($default, $condition);
}
}
+108
View File
@@ -0,0 +1,108 @@
<?php
namespace Illuminate\Support;
class HigherOrderWhenProxy
{
/**
* The target being conditionally operated on.
*
* @var mixed
*/
protected $target;
/**
* The condition for proxying.
*
* @var bool
*/
protected $condition;
/**
* Indicates whether the proxy has a condition.
*
* @var bool
*/
protected $hasCondition = false;
/**
* Determine whether the condition should be negated.
*
* @var bool
*/
protected $negateConditionOnCapture;
/**
* Create a new proxy instance.
*
* @param mixed $target
*/
public function __construct($target)
{
$this->target = $target;
}
/**
* Set the condition on the proxy.
*
* @param bool $condition
* @return $this
*/
public function condition($condition)
{
[$this->condition, $this->hasCondition] = [$condition, true];
return $this;
}
/**
* Indicate that the condition should be negated.
*
* @return $this
*/
public function negateConditionOnCapture()
{
$this->negateConditionOnCapture = true;
return $this;
}
/**
* Proxy accessing an attribute onto the target.
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
if (! $this->hasCondition) {
$condition = $this->target->{$key};
return $this->condition($this->negateConditionOnCapture ? ! $condition : $condition);
}
return $this->condition
? $this->target->{$key}
: $this->target;
}
/**
* Proxy a method call on the target.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
if (! $this->hasCondition) {
$condition = $this->target->{$method}(...$parameters);
return $this->condition($this->negateConditionOnCapture ? ! $condition : $condition);
}
return $this->condition
? $this->target->{$method}(...$parameters)
: $this->target;
}
}
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Taylor Otwell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -0,0 +1,73 @@
<?php
namespace Illuminate\Support\Traits;
use Closure;
use Illuminate\Support\HigherOrderWhenProxy;
trait Conditionable
{
/**
* Apply the callback if the given "value" is (or resolves to) truthy.
*
* @template TWhenParameter
* @template TWhenReturnType
*
* @param (\Closure($this): TWhenParameter)|TWhenParameter|null $value
* @param (callable($this, TWhenParameter): TWhenReturnType)|null $callback
* @param (callable($this, TWhenParameter): TWhenReturnType)|null $default
* @return $this|TWhenReturnType
*/
public function when($value = null, ?callable $callback = null, ?callable $default = null)
{
$value = $value instanceof Closure ? $value($this) : $value;
if (func_num_args() === 0) {
return new HigherOrderWhenProxy($this);
}
if (func_num_args() === 1) {
return (new HigherOrderWhenProxy($this))->condition($value);
}
if ($value) {
return $callback($this, $value) ?? $this;
} elseif ($default) {
return $default($this, $value) ?? $this;
}
return $this;
}
/**
* Apply the callback if the given "value" is (or resolves to) falsy.
*
* @template TUnlessParameter
* @template TUnlessReturnType
*
* @param (\Closure($this): TUnlessParameter)|TUnlessParameter|null $value
* @param (callable($this, TUnlessParameter): TUnlessReturnType)|null $callback
* @param (callable($this, TUnlessParameter): TUnlessReturnType)|null $default
* @return $this|TUnlessReturnType
*/
public function unless($value = null, ?callable $callback = null, ?callable $default = null)
{
$value = $value instanceof Closure ? $value($this) : $value;
if (func_num_args() === 0) {
return (new HigherOrderWhenProxy($this))->negateConditionOnCapture();
}
if (func_num_args() === 1) {
return (new HigherOrderWhenProxy($this))->condition(! $value);
}
if (! $value) {
return $callback($this, $value) ?? $this;
} elseif ($default) {
return $default($this, $value) ?? $this;
}
return $this;
}
}
+33
View File
@@ -0,0 +1,33 @@
{
"name": "illuminate/conditionable",
"description": "The Illuminate Conditionable package.",
"license": "MIT",
"homepage": "https://laravel.com",
"support": {
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"require": {
"php": "^8.2"
},
"autoload": {
"psr-4": {
"Illuminate\\Support\\": ""
}
},
"extra": {
"branch-alias": {
"dev-master": "12.x-dev"
}
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev"
}
+30
View File
@@ -0,0 +1,30 @@
<?php
namespace Illuminate\Container\Attributes;
use Attribute;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Container\ContextualAttribute;
#[Attribute(Attribute::TARGET_PARAMETER)]
class Auth implements ContextualAttribute
{
/**
* Create a new class instance.
*/
public function __construct(public ?string $guard = null)
{
}
/**
* Resolve the authentication guard.
*
* @param self $attribute
* @param \Illuminate\Contracts\Container\Container $container
* @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
*/
public static function resolve(self $attribute, Container $container)
{
return $container->make('auth')->guard($attribute->guard);
}
}
@@ -0,0 +1,30 @@
<?php
namespace Illuminate\Container\Attributes;
use Attribute;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Container\ContextualAttribute;
#[Attribute(Attribute::TARGET_PARAMETER)]
class Authenticated implements ContextualAttribute
{
/**
* Create a new class instance.
*/
public function __construct(public ?string $guard = null)
{
}
/**
* Resolve the currently authenticated user.
*
* @param self $attribute
* @param \Illuminate\Contracts\Container\Container $container
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public static function resolve(self $attribute, Container $container)
{
return call_user_func($container->make('auth')->userResolver(), $attribute->guard);
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
namespace Illuminate\Container\Attributes;
use Attribute;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Container\ContextualAttribute;
#[Attribute(Attribute::TARGET_PARAMETER)]
class Cache implements ContextualAttribute
{
/**
* Create a new class instance.
*/
public function __construct(public ?string $store = null)
{
}
/**
* Resolve the cache store.
*
* @param self $attribute
* @param \Illuminate\Contracts\Container\Container $container
* @return \Illuminate\Contracts\Cache\Repository
*/
public static function resolve(self $attribute, Container $container)
{
return $container->make('cache')->store($attribute->store);
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
namespace Illuminate\Container\Attributes;
use Attribute;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Container\ContextualAttribute;
#[Attribute(Attribute::TARGET_PARAMETER)]
class Config implements ContextualAttribute
{
/**
* Create a new class instance.
*/
public function __construct(public string $key, public mixed $default = null)
{
}
/**
* Resolve the configuration value.
*
* @param self $attribute
* @param \Illuminate\Contracts\Container\Container $container
* @return mixed
*/
public static function resolve(self $attribute, Container $container)
{
return $container->make('config')->get($attribute->key, $attribute->default);
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace Illuminate\Container\Attributes;
use Attribute;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Container\ContextualAttribute;
use Illuminate\Log\Context\Repository;
#[Attribute(Attribute::TARGET_PARAMETER)]
class Context implements ContextualAttribute
{
/**
* Create a new attribute instance.
*/
public function __construct(public string $key, public mixed $default = null, public bool $hidden = false)
{
}
/**
* Resolve the context value.
*
* @param self $attribute
* @param \Illuminate\Contracts\Container\Container $container
* @return mixed
*/
public static function resolve(self $attribute, Container $container): mixed
{
$repository = $container->make(Repository::class);
return match ($attribute->hidden) {
true => $repository->getHidden($attribute->key, $attribute->default),
false => $repository->get($attribute->key, $attribute->default),
};
}
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace Illuminate\Container\Attributes;
use Attribute;
#[Attribute(Attribute::TARGET_PARAMETER)]
class CurrentUser extends Authenticated
{
//
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace Illuminate\Container\Attributes;
use Attribute;
#[Attribute(Attribute::TARGET_PARAMETER)]
class DB extends Database
{
//
}
+30
View File
@@ -0,0 +1,30 @@
<?php
namespace Illuminate\Container\Attributes;
use Attribute;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Container\ContextualAttribute;
#[Attribute(Attribute::TARGET_PARAMETER)]
class Database implements ContextualAttribute
{
/**
* Create a new class instance.
*/
public function __construct(public ?string $connection = null)
{
}
/**
* Resolve the database connection.
*
* @param self $attribute
* @param \Illuminate\Contracts\Container\Container $container
* @return \Illuminate\Database\Connection
*/
public static function resolve(self $attribute, Container $container)
{
return $container->make('db')->connection($attribute->connection);
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
namespace Illuminate\Container\Attributes;
use Attribute;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Container\ContextualAttribute;
#[Attribute(Attribute::TARGET_PARAMETER)]
class Give implements ContextualAttribute
{
/**
* Provide a concrete class implementation for dependency injection.
*
* @template T
*
* @param class-string<T> $class
* @param array|null $params
*/
public function __construct(
public string $class,
public array $params = []
) {
}
/**
* Resolve the dependency.
*
* @param self $attribute
* @param \Illuminate\Contracts\Container\Container $container
* @return mixed
*/
public static function resolve(self $attribute, Container $container): mixed
{
return $container->make($attribute->class, $attribute->params);
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
namespace Illuminate\Container\Attributes;
use Attribute;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Container\ContextualAttribute;
#[Attribute(Attribute::TARGET_PARAMETER)]
class Log implements ContextualAttribute
{
/**
* Create a new class instance.
*/
public function __construct(public ?string $channel = null)
{
}
/**
* Resolve the log channel.
*
* @param self $attribute
* @param \Illuminate\Contracts\Container\Container $container
* @return \Psr\Log\LoggerInterface
*/
public static function resolve(self $attribute, Container $container)
{
return $container->make('log')->channel($attribute->channel);
}
}
@@ -0,0 +1,30 @@
<?php
namespace Illuminate\Container\Attributes;
use Attribute;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Container\ContextualAttribute;
#[Attribute(Attribute::TARGET_PARAMETER)]
class RouteParameter implements ContextualAttribute
{
/**
* Create a new class instance.
*/
public function __construct(public string $parameter)
{
}
/**
* Resolve the route parameter.
*
* @param self $attribute
* @param \Illuminate\Contracts\Container\Container $container
* @return mixed
*/
public static function resolve(self $attribute, Container $container)
{
return $container->make('request')->route($attribute->parameter);
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
namespace Illuminate\Container\Attributes;
use Attribute;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Container\ContextualAttribute;
#[Attribute(Attribute::TARGET_PARAMETER)]
class Storage implements ContextualAttribute
{
/**
* Create a new class instance.
*/
public function __construct(public ?string $disk = null)
{
}
/**
* Resolve the storage disk.
*
* @param self $attribute
* @param \Illuminate\Contracts\Container\Container $container
* @return \Illuminate\Contracts\Filesystem\Filesystem
*/
public static function resolve(self $attribute, Container $container)
{
return $container->make('filesystem')->disk($attribute->disk);
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Illuminate\Container\Attributes;
use Attribute;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Container\ContextualAttribute;
#[Attribute(Attribute::TARGET_PARAMETER)]
final class Tag implements ContextualAttribute
{
public function __construct(
public string $tag,
) {
}
/**
* Resolve the tag.
*
* @param self $attribute
* @param \Illuminate\Contracts\Container\Container $container
* @return mixed
*/
public static function resolve(self $attribute, Container $container)
{
return $container->tagged($attribute->tag);
}
}
+218
View File
@@ -0,0 +1,218 @@
<?php
namespace Illuminate\Container;
use Closure;
use Illuminate\Contracts\Container\BindingResolutionException;
use InvalidArgumentException;
use ReflectionFunction;
use ReflectionMethod;
class BoundMethod
{
/**
* Call the given Closure / class@method and inject its dependencies.
*
* @param \Illuminate\Container\Container $container
* @param callable|string $callback
* @param array $parameters
* @param string|null $defaultMethod
* @return mixed
*
* @throws \ReflectionException
* @throws \InvalidArgumentException
*/
public static function call($container, $callback, array $parameters = [], $defaultMethod = null)
{
if (is_string($callback) && ! $defaultMethod && method_exists($callback, '__invoke')) {
$defaultMethod = '__invoke';
}
if (static::isCallableWithAtSign($callback) || $defaultMethod) {
return static::callClass($container, $callback, $parameters, $defaultMethod);
}
return static::callBoundMethod($container, $callback, function () use ($container, $callback, $parameters) {
return $callback(...array_values(static::getMethodDependencies($container, $callback, $parameters)));
});
}
/**
* Call a string reference to a class using Class@method syntax.
*
* @param \Illuminate\Container\Container $container
* @param string $target
* @param array $parameters
* @param string|null $defaultMethod
* @return mixed
*
* @throws \InvalidArgumentException
*/
protected static function callClass($container, $target, array $parameters = [], $defaultMethod = null)
{
$segments = explode('@', $target);
// We will assume an @ sign is used to delimit the class name from the method
// name. We will split on this @ sign and then build a callable array that
// we can pass right back into the "call" method for dependency binding.
$method = count($segments) === 2
? $segments[1]
: $defaultMethod;
if (is_null($method)) {
throw new InvalidArgumentException('Method not provided.');
}
return static::call(
$container,
[$container->make($segments[0]), $method],
$parameters
);
}
/**
* Call a method that has been bound to the container.
*
* @param \Illuminate\Container\Container $container
* @param callable $callback
* @param mixed $default
* @return mixed
*/
protected static function callBoundMethod($container, $callback, $default)
{
if (! is_array($callback)) {
return Util::unwrapIfClosure($default);
}
// Here we need to turn the array callable into a Class@method string we can use to
// examine the container and see if there are any method bindings for this given
// method. If there are, we can call this method binding callback immediately.
$method = static::normalizeMethod($callback);
if ($container->hasMethodBinding($method)) {
return $container->callMethodBinding($method, $callback[0]);
}
return Util::unwrapIfClosure($default);
}
/**
* Normalize the given callback into a Class@method string.
*
* @param callable $callback
* @return string
*/
protected static function normalizeMethod($callback)
{
$class = is_string($callback[0]) ? $callback[0] : get_class($callback[0]);
return "{$class}@{$callback[1]}";
}
/**
* Get all dependencies for a given method.
*
* @param \Illuminate\Container\Container $container
* @param callable|string $callback
* @param array $parameters
* @return array
*
* @throws \ReflectionException
*/
protected static function getMethodDependencies($container, $callback, array $parameters = [])
{
$dependencies = [];
foreach (static::getCallReflector($callback)->getParameters() as $parameter) {
static::addDependencyForCallParameter($container, $parameter, $parameters, $dependencies);
}
return array_merge($dependencies, array_values($parameters));
}
/**
* Get the proper reflection instance for the given callback.
*
* @param callable|string $callback
* @return \ReflectionFunctionAbstract
*
* @throws \ReflectionException
*/
protected static function getCallReflector($callback)
{
if (is_string($callback) && str_contains($callback, '::')) {
$callback = explode('::', $callback);
} elseif (is_object($callback) && ! $callback instanceof Closure) {
$callback = [$callback, '__invoke'];
}
return is_array($callback)
? new ReflectionMethod($callback[0], $callback[1])
: new ReflectionFunction($callback);
}
/**
* Get the dependency for the given call parameter.
*
* @param \Illuminate\Container\Container $container
* @param \ReflectionParameter $parameter
* @param array $parameters
* @param array $dependencies
* @return void
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
protected static function addDependencyForCallParameter(
$container,
$parameter,
array &$parameters,
&$dependencies
) {
$pendingDependencies = [];
if (array_key_exists($paramName = $parameter->getName(), $parameters)) {
$pendingDependencies[] = $parameters[$paramName];
unset($parameters[$paramName]);
} elseif ($attribute = Util::getContextualAttributeFromDependency($parameter)) {
$pendingDependencies[] = $container->resolveFromAttribute($attribute);
} elseif (! is_null($className = Util::getParameterClassName($parameter))) {
if (array_key_exists($className, $parameters)) {
$pendingDependencies[] = $parameters[$className];
unset($parameters[$className]);
} elseif ($parameter->isVariadic()) {
$variadicDependencies = $container->make($className);
$pendingDependencies = array_merge($pendingDependencies, is_array($variadicDependencies)
? $variadicDependencies
: [$variadicDependencies]);
} else {
$pendingDependencies[] = $container->make($className);
}
} elseif ($parameter->isDefaultValueAvailable()) {
$pendingDependencies[] = $parameter->getDefaultValue();
} elseif (! $parameter->isOptional() && ! array_key_exists($paramName, $parameters)) {
$message = "Unable to resolve dependency [{$parameter}] in class {$parameter->getDeclaringClass()->getName()}";
throw new BindingResolutionException($message);
}
foreach ($pendingDependencies as $dependency) {
$container->fireAfterResolvingAttributeCallbacks($parameter->getAttributes(), $dependency);
}
$dependencies = array_merge($dependencies, $pendingDependencies);
}
/**
* Determine if the given string is in Class@method syntax.
*
* @param mixed $callback
* @return bool
*/
protected static function isCallableWithAtSign($callback)
{
return is_string($callback) && str_contains($callback, '@');
}
}
+1701
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,95 @@
<?php
namespace Illuminate\Container;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Container\ContextualBindingBuilder as ContextualBindingBuilderContract;
class ContextualBindingBuilder implements ContextualBindingBuilderContract
{
/**
* The underlying container instance.
*
* @var \Illuminate\Contracts\Container\Container
*/
protected $container;
/**
* The concrete instance.
*
* @var string|array
*/
protected $concrete;
/**
* The abstract target.
*
* @var string
*/
protected $needs;
/**
* Create a new contextual binding builder.
*
* @param \Illuminate\Contracts\Container\Container $container
* @param string|array $concrete
*/
public function __construct(Container $container, $concrete)
{
$this->concrete = $concrete;
$this->container = $container;
}
/**
* Define the abstract target that depends on the context.
*
* @param string $abstract
* @return $this
*/
public function needs($abstract)
{
$this->needs = $abstract;
return $this;
}
/**
* Define the implementation for the contextual binding.
*
* @param \Closure|string|array $implementation
* @return void
*/
public function give($implementation)
{
foreach (Util::arrayWrap($this->concrete) as $concrete) {
$this->container->addContextualBinding($concrete, $this->needs, $implementation);
}
}
/**
* Define tagged services to be used as the implementation for the contextual binding.
*
* @param string $tag
* @return void
*/
public function giveTagged($tag)
{
$this->give(function ($container) use ($tag) {
$taggedServices = $container->tagged($tag);
return is_array($taggedServices) ? $taggedServices : iterator_to_array($taggedServices);
});
}
/**
* Specify the configuration item to bind as a primitive.
*
* @param string $key
* @param mixed $default
* @return void
*/
public function giveConfig($key, $default = null)
{
$this->give(fn ($container) => $container->get('config')->get($key, $default));
}
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace Illuminate\Container;
use Exception;
use Psr\Container\NotFoundExceptionInterface;
class EntryNotFoundException extends Exception implements NotFoundExceptionInterface
{
//
}
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Taylor Otwell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+60
View File
@@ -0,0 +1,60 @@
<?php
namespace Illuminate\Container;
use Countable;
use IteratorAggregate;
use Traversable;
class RewindableGenerator implements Countable, IteratorAggregate
{
/**
* The generator callback.
*
* @var callable
*/
protected $generator;
/**
* The number of tagged services.
*
* @var callable|int
*/
protected $count;
/**
* Create a new generator instance.
*
* @param callable $generator
* @param callable|int $count
*/
public function __construct(callable $generator, $count)
{
$this->count = $count;
$this->generator = $generator;
}
/**
* Get an iterator from the generator.
*
* @return \Traversable
*/
public function getIterator(): Traversable
{
return ($this->generator)();
}
/**
* Get the total number of tagged services.
*
* @return int
*/
public function count(): int
{
if (is_callable($count = $this->count)) {
$this->count = $count();
}
return $this->count;
}
}
+87
View File
@@ -0,0 +1,87 @@
<?php
namespace Illuminate\Container;
use Closure;
use Illuminate\Contracts\Container\ContextualAttribute;
use ReflectionAttribute;
use ReflectionNamedType;
/**
* @internal
*/
class Util
{
/**
* If the given value is not an array and not null, wrap it in one.
*
* From Arr::wrap() in Illuminate\Support.
*
* @param mixed $value
* @return array
*/
public static function arrayWrap($value)
{
if (is_null($value)) {
return [];
}
return is_array($value) ? $value : [$value];
}
/**
* Return the default value of the given value.
*
* From global value() helper in Illuminate\Support.
*
* @param mixed $value
* @param mixed ...$args
* @return mixed
*/
public static function unwrapIfClosure($value, ...$args)
{
return $value instanceof Closure ? $value(...$args) : $value;
}
/**
* Get the class name of the given parameter's type, if possible.
*
* From Reflector::getParameterClassName() in Illuminate\Support.
*
* @param \ReflectionParameter $parameter
* @return string|null
*/
public static function getParameterClassName($parameter)
{
$type = $parameter->getType();
if (! $type instanceof ReflectionNamedType || $type->isBuiltin()) {
return null;
}
$name = $type->getName();
if (! is_null($class = $parameter->getDeclaringClass())) {
if ($name === 'self') {
return $class->getName();
}
if ($name === 'parent' && $parent = $class->getParentClass()) {
return $parent->getName();
}
}
return $name;
}
/**
* Get a contextual attribute from a dependency.
*
* @param \ReflectionParameter $dependency
* @return \ReflectionAttribute|null
*/
public static function getContextualAttributeFromDependency($dependency)
{
return $dependency->getAttributes(ContextualAttribute::class, ReflectionAttribute::IS_INSTANCEOF)[0] ?? null;
}
}
+38
View File
@@ -0,0 +1,38 @@
{
"name": "illuminate/container",
"description": "The Illuminate Container package.",
"license": "MIT",
"homepage": "https://laravel.com",
"support": {
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"require": {
"php": "^8.2",
"illuminate/contracts": "^12.0",
"psr/container": "^1.1.1|^2.0.1"
},
"provide": {
"psr/container-implementation": "1.1|2.0"
},
"autoload": {
"psr-4": {
"Illuminate\\Container\\": ""
}
},
"extra": {
"branch-alias": {
"dev-master": "12.x-dev"
}
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev"
}
@@ -0,0 +1,15 @@
<?php
namespace Illuminate\Contracts\Auth\Access;
interface Authorizable
{
/**
* Determine if the entity has a given ability.
*
* @param iterable|string $abilities
* @param array|mixed $arguments
* @return bool
*/
public function can($abilities, $arguments = []);
}
+150
View File
@@ -0,0 +1,150 @@
<?php
namespace Illuminate\Contracts\Auth\Access;
interface Gate
{
/**
* Determine if a given ability has been defined.
*
* @param string $ability
* @return bool
*/
public function has($ability);
/**
* Define a new ability.
*
* @param string $ability
* @param callable|string $callback
* @return $this
*/
public function define($ability, $callback);
/**
* Define abilities for a resource.
*
* @param string $name
* @param string $class
* @param array|null $abilities
* @return $this
*/
public function resource($name, $class, ?array $abilities = null);
/**
* Define a policy class for a given class type.
*
* @param string $class
* @param string $policy
* @return $this
*/
public function policy($class, $policy);
/**
* Register a callback to run before all Gate checks.
*
* @param callable $callback
* @return $this
*/
public function before(callable $callback);
/**
* Register a callback to run after all Gate checks.
*
* @param callable $callback
* @return $this
*/
public function after(callable $callback);
/**
* Determine if all of the given abilities should be granted for the current user.
*
* @param iterable|string $ability
* @param array|mixed $arguments
* @return bool
*/
public function allows($ability, $arguments = []);
/**
* Determine if any of the given abilities should be denied for the current user.
*
* @param iterable|string $ability
* @param array|mixed $arguments
* @return bool
*/
public function denies($ability, $arguments = []);
/**
* Determine if all of the given abilities should be granted for the current user.
*
* @param iterable|string $abilities
* @param array|mixed $arguments
* @return bool
*/
public function check($abilities, $arguments = []);
/**
* Determine if any one of the given abilities should be granted for the current user.
*
* @param iterable|string $abilities
* @param array|mixed $arguments
* @return bool
*/
public function any($abilities, $arguments = []);
/**
* Determine if the given ability should be granted for the current user.
*
* @param string $ability
* @param array|mixed $arguments
* @return \Illuminate\Auth\Access\Response
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function authorize($ability, $arguments = []);
/**
* Inspect the user for the given ability.
*
* @param string $ability
* @param array|mixed $arguments
* @return \Illuminate\Auth\Access\Response
*/
public function inspect($ability, $arguments = []);
/**
* Get the raw result from the authorization callback.
*
* @param string $ability
* @param array|mixed $arguments
* @return mixed
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function raw($ability, $arguments = []);
/**
* Get a policy instance for a given class.
*
* @param object|string $class
* @return mixed
*
* @throws \InvalidArgumentException
*/
public function getPolicyFor($class);
/**
* Get a guard instance for the given user.
*
* @param \Illuminate\Contracts\Auth\Authenticatable|mixed $user
* @return static
*/
public function forUser($user);
/**
* Get all of the defined abilities.
*
* @return array
*/
public function abilities();
}
+56
View File
@@ -0,0 +1,56 @@
<?php
namespace Illuminate\Contracts\Auth;
interface Authenticatable
{
/**
* Get the name of the unique identifier for the user.
*
* @return string
*/
public function getAuthIdentifierName();
/**
* Get the unique identifier for the user.
*
* @return mixed
*/
public function getAuthIdentifier();
/**
* Get the name of the password attribute for the user.
*
* @return string
*/
public function getAuthPasswordName();
/**
* Get the password for the user.
*
* @return string
*/
public function getAuthPassword();
/**
* Get the token value for the "remember me" session.
*
* @return string
*/
public function getRememberToken();
/**
* Set the token value for the "remember me" session.
*
* @param string $value
* @return void
*/
public function setRememberToken($value);
/**
* Get the column name for the "remember me" token.
*
* @return string
*/
public function getRememberTokenName();
}
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace Illuminate\Contracts\Auth;
interface CanResetPassword
{
/**
* Get the e-mail address where password reset links are sent.
*
* @return string
*/
public function getEmailForPasswordReset();
/**
* Send the password reset notification.
*
* @param string $token
* @return void
*/
public function sendPasswordResetNotification($token);
}
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace Illuminate\Contracts\Auth;
interface Factory
{
/**
* Get a guard instance by name.
*
* @param string|null $name
* @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
*/
public function guard($name = null);
/**
* Set the default guard the factory should serve.
*
* @param string $name
* @return void
*/
public function shouldUse($name);
}
+57
View File
@@ -0,0 +1,57 @@
<?php
namespace Illuminate\Contracts\Auth;
interface Guard
{
/**
* Determine if the current user is authenticated.
*
* @return bool
*/
public function check();
/**
* Determine if the current user is a guest.
*
* @return bool
*/
public function guest();
/**
* Get the currently authenticated user.
*
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user();
/**
* Get the ID for the currently authenticated user.
*
* @return int|string|null
*/
public function id();
/**
* Validate a user's credentials.
*
* @param array $credentials
* @return bool
*/
public function validate(array $credentials = []);
/**
* Determine if the guard has a user instance.
*
* @return bool
*/
public function hasUser();
/**
* Set the current user.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return $this
*/
public function setUser(Authenticatable $user);
}
@@ -0,0 +1,8 @@
<?php
namespace Illuminate\Contracts\Auth\Middleware;
interface AuthenticatesRequests
{
//
}
+34
View File
@@ -0,0 +1,34 @@
<?php
namespace Illuminate\Contracts\Auth;
interface MustVerifyEmail
{
/**
* Determine if the user has verified their email address.
*
* @return bool
*/
public function hasVerifiedEmail();
/**
* Mark the given user's email as verified.
*
* @return bool
*/
public function markEmailAsVerified();
/**
* Send the email verification notification.
*
* @return void
*/
public function sendEmailVerificationNotification();
/**
* Get the email address that should be used for verification.
*
* @return string
*/
public function getEmailForVerification();
}
+61
View File
@@ -0,0 +1,61 @@
<?php
namespace Illuminate\Contracts\Auth;
use Closure;
interface PasswordBroker
{
/**
* Constant representing a successfully sent reminder.
*
* @var string
*/
const RESET_LINK_SENT = 'passwords.sent';
/**
* Constant representing a successfully reset password.
*
* @var string
*/
const PASSWORD_RESET = 'passwords.reset';
/**
* Constant representing the user not found response.
*
* @var string
*/
const INVALID_USER = 'passwords.user';
/**
* Constant representing an invalid token.
*
* @var string
*/
const INVALID_TOKEN = 'passwords.token';
/**
* Constant representing a throttled reset attempt.
*
* @var string
*/
const RESET_THROTTLED = 'passwords.throttled';
/**
* Send a password reset link to a user.
*
* @param array $credentials
* @param \Closure|null $callback
* @return string
*/
public function sendResetLink(array $credentials, ?Closure $callback = null);
/**
* Reset the password for the given token.
*
* @param array $credentials
* @param \Closure $callback
* @return mixed
*/
public function reset(array $credentials, Closure $callback);
}
@@ -0,0 +1,14 @@
<?php
namespace Illuminate\Contracts\Auth;
interface PasswordBrokerFactory
{
/**
* Get a password broker instance by name.
*
* @param string|null $name
* @return \Illuminate\Contracts\Auth\PasswordBroker
*/
public function broker($name = null);
}
+63
View File
@@ -0,0 +1,63 @@
<?php
namespace Illuminate\Contracts\Auth;
interface StatefulGuard extends Guard
{
/**
* Attempt to authenticate a user using the given credentials.
*
* @param array $credentials
* @param bool $remember
* @return bool
*/
public function attempt(array $credentials = [], $remember = false);
/**
* Log a user into the application without sessions or cookies.
*
* @param array $credentials
* @return bool
*/
public function once(array $credentials = []);
/**
* Log a user into the application.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param bool $remember
* @return void
*/
public function login(Authenticatable $user, $remember = false);
/**
* Log the given user ID into the application.
*
* @param mixed $id
* @param bool $remember
* @return \Illuminate\Contracts\Auth\Authenticatable|false
*/
public function loginUsingId($id, $remember = false);
/**
* Log the given user ID into the application without sessions or cookies.
*
* @param mixed $id
* @return \Illuminate\Contracts\Auth\Authenticatable|false
*/
public function onceUsingId($id);
/**
* Determine if the user was authenticated via "remember me" cookie.
*
* @return bool
*/
public function viaRemember();
/**
* Log the user out of the application.
*
* @return void
*/
public function logout();
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace Illuminate\Contracts\Auth;
interface SupportsBasicAuth
{
/**
* Attempt to authenticate using HTTP Basic Auth.
*
* @param string $field
* @param array $extraConditions
* @return \Symfony\Component\HttpFoundation\Response|null
*/
public function basic($field = 'email', $extraConditions = []);
/**
* Perform a stateless HTTP Basic login attempt.
*
* @param string $field
* @param array $extraConditions
* @return \Symfony\Component\HttpFoundation\Response|null
*/
public function onceBasic($field = 'email', $extraConditions = []);
}
+59
View File
@@ -0,0 +1,59 @@
<?php
namespace Illuminate\Contracts\Auth;
interface UserProvider
{
/**
* Retrieve a user by their unique identifier.
*
* @param mixed $identifier
* @return (\Illuminate\Contracts\Auth\Authenticatable&\Illuminate\Database\Eloquent\Model)|null
*/
public function retrieveById($identifier);
/**
* Retrieve a user by their unique identifier and "remember me" token.
*
* @param mixed $identifier
* @param string $token
* @return (\Illuminate\Contracts\Auth\Authenticatable&\Illuminate\Database\Eloquent\Model)|null
*/
public function retrieveByToken($identifier, #[\SensitiveParameter] $token);
/**
* Update the "remember me" token for the given user in storage.
*
* @param \Illuminate\Contracts\Auth\Authenticatable&\Illuminate\Database\Eloquent\Model $user
* @param string $token
* @return void
*/
public function updateRememberToken(Authenticatable $user, #[\SensitiveParameter] $token);
/**
* Retrieve a user by the given credentials.
*
* @param array $credentials
* @return (\Illuminate\Contracts\Auth\Authenticatable&\Illuminate\Database\Eloquent\Model)|null
*/
public function retrieveByCredentials(#[\SensitiveParameter] array $credentials);
/**
* Validate a user against the given credentials.
*
* @param \Illuminate\Contracts\Auth\Authenticatable&\Illuminate\Database\Eloquent\Model $user
* @param array $credentials
* @return bool
*/
public function validateCredentials(Authenticatable $user, #[\SensitiveParameter] array $credentials);
/**
* Rehash the user's password if required and supported.
*
* @param \Illuminate\Contracts\Auth\Authenticatable&\Illuminate\Database\Eloquent\Model $user
* @param array $credentials
* @param bool $force
* @return void
*/
public function rehashPasswordIfRequired(Authenticatable $user, #[\SensitiveParameter] array $credentials, bool $force = false);
}
@@ -0,0 +1,35 @@
<?php
namespace Illuminate\Contracts\Broadcasting;
interface Broadcaster
{
/**
* Authenticate the incoming request for a given channel.
*
* @param \Illuminate\Http\Request $request
* @return mixed
*/
public function auth($request);
/**
* Return the valid authentication response.
*
* @param \Illuminate\Http\Request $request
* @param mixed $result
* @return mixed
*/
public function validAuthenticationResponse($request, $result);
/**
* Broadcast the given event.
*
* @param array $channels
* @param string $event
* @param array $payload
* @return void
*
* @throws \Illuminate\Broadcasting\BroadcastException
*/
public function broadcast(array $channels, $event, array $payload = []);
}
+14
View File
@@ -0,0 +1,14 @@
<?php
namespace Illuminate\Contracts\Broadcasting;
interface Factory
{
/**
* Get a broadcaster implementation by name.
*
* @param string|null $name
* @return \Illuminate\Contracts\Broadcasting\Broadcaster
*/
public function connection($name = null);
}
@@ -0,0 +1,20 @@
<?php
namespace Illuminate\Contracts\Broadcasting;
interface HasBroadcastChannel
{
/**
* Get the broadcast channel route definition that is associated with the given entity.
*
* @return string
*/
public function broadcastChannelRoute();
/**
* Get the broadcast channel name that is associated with the given entity.
*
* @return string
*/
public function broadcastChannel();
}
@@ -0,0 +1,8 @@
<?php
namespace Illuminate\Contracts\Broadcasting;
interface ShouldBeUnique
{
//
}
@@ -0,0 +1,13 @@
<?php
namespace Illuminate\Contracts\Broadcasting;
interface ShouldBroadcast
{
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|\Illuminate\Broadcasting\Channel[]|string[]|string
*/
public function broadcastOn();
}
@@ -0,0 +1,8 @@
<?php
namespace Illuminate\Contracts\Broadcasting;
interface ShouldBroadcastNow extends ShouldBroadcast
{
//
}
@@ -0,0 +1,8 @@
<?php
namespace Illuminate\Contracts\Broadcasting;
interface ShouldRescue
{
//
}
+66
View File
@@ -0,0 +1,66 @@
<?php
namespace Illuminate\Contracts\Bus;
interface Dispatcher
{
/**
* Dispatch a command to its appropriate handler.
*
* @param mixed $command
* @return mixed
*/
public function dispatch($command);
/**
* Dispatch a command to its appropriate handler in the current process.
*
* Queueable jobs will be dispatched to the "sync" queue.
*
* @param mixed $command
* @param mixed $handler
* @return mixed
*/
public function dispatchSync($command, $handler = null);
/**
* Dispatch a command to its appropriate handler in the current process.
*
* @param mixed $command
* @param mixed $handler
* @return mixed
*/
public function dispatchNow($command, $handler = null);
/**
* Determine if the given command has a handler.
*
* @param mixed $command
* @return bool
*/
public function hasCommandHandler($command);
/**
* Retrieve the handler for a command.
*
* @param mixed $command
* @return bool|mixed
*/
public function getCommandHandler($command);
/**
* Set the pipes commands should be piped through before dispatching.
*
* @param array $pipes
* @return $this
*/
public function pipeThrough(array $pipes);
/**
* Map a command to a handler.
*
* @param array $map
* @return $this
*/
public function map(array $map);
}
+30
View File
@@ -0,0 +1,30 @@
<?php
namespace Illuminate\Contracts\Bus;
interface QueueingDispatcher extends Dispatcher
{
/**
* Attempt to find the batch with the given ID.
*
* @param string $batchId
* @return \Illuminate\Bus\Batch|null
*/
public function findBatch(string $batchId);
/**
* Create a new batch of queueable jobs.
*
* @param \Illuminate\Support\Collection|array $jobs
* @return \Illuminate\Bus\PendingBatch
*/
public function batch($jobs);
/**
* Dispatch a command to its appropriate handler behind a queue.
*
* @param mixed $command
* @return mixed
*/
public function dispatchToQueue($command);
}
+14
View File
@@ -0,0 +1,14 @@
<?php
namespace Illuminate\Contracts\Cache;
interface Factory
{
/**
* Get a cache store instance by name.
*
* @param string|null $name
* @return \Illuminate\Contracts\Cache\Repository
*/
public function store($name = null);
}
+46
View File
@@ -0,0 +1,46 @@
<?php
namespace Illuminate\Contracts\Cache;
interface Lock
{
/**
* Attempt to acquire the lock.
*
* @param callable|null $callback
* @return mixed
*/
public function get($callback = null);
/**
* Attempt to acquire the lock for the given number of seconds.
*
* @param int $seconds
* @param callable|null $callback
* @return mixed
*
* @throws \Illuminate\Contracts\Cache\LockTimeoutException
*/
public function block($seconds, $callback = null);
/**
* Release the lock.
*
* @return bool
*/
public function release();
/**
* Returns the current owner of the lock.
*
* @return string
*/
public function owner();
/**
* Releases this lock in disregard of ownership.
*
* @return void
*/
public function forceRelease();
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace Illuminate\Contracts\Cache;
interface LockProvider
{
/**
* Get a lock instance.
*
* @param string $name
* @param int $seconds
* @param string|null $owner
* @return \Illuminate\Contracts\Cache\Lock
*/
public function lock($name, $seconds = 0, $owner = null);
/**
* Restore a lock instance using the owner identifier.
*
* @param string $name
* @param string $owner
* @return \Illuminate\Contracts\Cache\Lock
*/
public function restoreLock($name, $owner);
}
@@ -0,0 +1,10 @@
<?php
namespace Illuminate\Contracts\Cache;
use Exception;
class LockTimeoutException extends Exception
{
//
}
+116
View File
@@ -0,0 +1,116 @@
<?php
namespace Illuminate\Contracts\Cache;
use Closure;
use Psr\SimpleCache\CacheInterface;
interface Repository extends CacheInterface
{
/**
* Retrieve an item from the cache and delete it.
*
* @template TCacheValue
*
* @param array|string $key
* @param TCacheValue|(\Closure(): TCacheValue) $default
* @return (TCacheValue is null ? mixed : TCacheValue)
*/
public function pull($key, $default = null);
/**
* Store an item in the cache.
*
* @param string $key
* @param mixed $value
* @param \DateTimeInterface|\DateInterval|int|null $ttl
* @return bool
*/
public function put($key, $value, $ttl = null);
/**
* Store an item in the cache if the key does not exist.
*
* @param string $key
* @param mixed $value
* @param \DateTimeInterface|\DateInterval|int|null $ttl
* @return bool
*/
public function add($key, $value, $ttl = null);
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return int|bool
*/
public function increment($key, $value = 1);
/**
* Decrement the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return int|bool
*/
public function decrement($key, $value = 1);
/**
* Store an item in the cache indefinitely.
*
* @param string $key
* @param mixed $value
* @return bool
*/
public function forever($key, $value);
/**
* Get an item from the cache, or execute the given Closure and store the result.
*
* @template TCacheValue
*
* @param string $key
* @param \DateTimeInterface|\DateInterval|\Closure|int|null $ttl
* @param \Closure(): TCacheValue $callback
* @return TCacheValue
*/
public function remember($key, $ttl, Closure $callback);
/**
* Get an item from the cache, or execute the given Closure and store the result forever.
*
* @template TCacheValue
*
* @param string $key
* @param \Closure(): TCacheValue $callback
* @return TCacheValue
*/
public function sear($key, Closure $callback);
/**
* Get an item from the cache, or execute the given Closure and store the result forever.
*
* @template TCacheValue
*
* @param string $key
* @param \Closure(): TCacheValue $callback
* @return TCacheValue
*/
public function rememberForever($key, Closure $callback);
/**
* Remove an item from the cache.
*
* @param string $key
* @return bool
*/
public function forget($key);
/**
* Get the cache store implementation.
*
* @return \Illuminate\Contracts\Cache\Store
*/
public function getStore();
}
+92
View File
@@ -0,0 +1,92 @@
<?php
namespace Illuminate\Contracts\Cache;
interface Store
{
/**
* Retrieve an item from the cache by key.
*
* @param string $key
* @return mixed
*/
public function get($key);
/**
* Retrieve multiple items from the cache by key.
*
* Items not found in the cache will have a null value.
*
* @param array $keys
* @return array
*/
public function many(array $keys);
/**
* Store an item in the cache for a given number of seconds.
*
* @param string $key
* @param mixed $value
* @param int $seconds
* @return bool
*/
public function put($key, $value, $seconds);
/**
* Store multiple items in the cache for a given number of seconds.
*
* @param array $values
* @param int $seconds
* @return bool
*/
public function putMany(array $values, $seconds);
/**
* Increment the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return int|bool
*/
public function increment($key, $value = 1);
/**
* Decrement the value of an item in the cache.
*
* @param string $key
* @param mixed $value
* @return int|bool
*/
public function decrement($key, $value = 1);
/**
* Store an item in the cache indefinitely.
*
* @param string $key
* @param mixed $value
* @return bool
*/
public function forever($key, $value);
/**
* Remove an item from the cache.
*
* @param string $key
* @return bool
*/
public function forget($key);
/**
* Remove all items from the cache.
*
* @return bool
*/
public function flush();
/**
* Get the cache key prefix.
*
* @return string
*/
public function getPrefix();
}
+19
View File
@@ -0,0 +1,19 @@
<?php
namespace Illuminate\Contracts\Concurrency;
use Closure;
use Illuminate\Support\Defer\DeferredCallback;
interface Driver
{
/**
* Run the given tasks concurrently and return an array containing the results.
*/
public function run(Closure|array $tasks): array;
/**
* Defer the execution of the given tasks.
*/
public function defer(Closure|array $tasks): DeferredCallback;
}
+57
View File
@@ -0,0 +1,57 @@
<?php
namespace Illuminate\Contracts\Config;
interface Repository
{
/**
* Determine if the given configuration value exists.
*
* @param string $key
* @return bool
*/
public function has($key);
/**
* Get the specified configuration value.
*
* @param array|string $key
* @param mixed $default
* @return mixed
*/
public function get($key, $default = null);
/**
* Get all of the configuration items for the application.
*
* @return array
*/
public function all();
/**
* Set a given configuration value.
*
* @param array|string $key
* @param mixed $value
* @return void
*/
public function set($key, $value = null);
/**
* Prepend a value onto an array configuration value.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function prepend($key, $value);
/**
* Push a value onto an array configuration value.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function push($key, $value);
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace Illuminate\Contracts\Console;
interface Application
{
/**
* Run an Artisan console command by name.
*
* @param string $command
* @param array $parameters
* @param \Symfony\Component\Console\Output\OutputInterface|null $outputBuffer
* @return int
*/
public function call($command, array $parameters = [], $outputBuffer = null);
/**
* Get the output from the last command.
*
* @return string
*/
public function output();
}
+8
View File
@@ -0,0 +1,8 @@
<?php
namespace Illuminate\Contracts\Console;
interface Isolatable
{
//
}
+64
View File
@@ -0,0 +1,64 @@
<?php
namespace Illuminate\Contracts\Console;
interface Kernel
{
/**
* Bootstrap the application for artisan commands.
*
* @return void
*/
public function bootstrap();
/**
* Handle an incoming console command.
*
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface|null $output
* @return int
*/
public function handle($input, $output = null);
/**
* Run an Artisan console command by name.
*
* @param string $command
* @param array $parameters
* @param \Symfony\Component\Console\Output\OutputInterface|null $outputBuffer
* @return int
*/
public function call($command, array $parameters = [], $outputBuffer = null);
/**
* Queue an Artisan console command by name.
*
* @param string $command
* @param array $parameters
* @return \Illuminate\Foundation\Bus\PendingDispatch
*/
public function queue($command, array $parameters = []);
/**
* Get all of the commands registered with the console.
*
* @return array
*/
public function all();
/**
* Get the output for the last run command.
*
* @return string
*/
public function output();
/**
* Terminate the application.
*
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param int $status
* @return void
*/
public function terminate($input, $status);
}
@@ -0,0 +1,8 @@
<?php
namespace Illuminate\Contracts\Console;
interface PromptsForMissingInput
{
//
}
@@ -0,0 +1,11 @@
<?php
namespace Illuminate\Contracts\Container;
use Exception;
use Psr\Container\ContainerExceptionInterface;
class BindingResolutionException extends Exception implements ContainerExceptionInterface
{
//
}
@@ -0,0 +1,11 @@
<?php
namespace Illuminate\Contracts\Container;
use Exception;
use Psr\Container\ContainerExceptionInterface;
class CircularDependencyException extends Exception implements ContainerExceptionInterface
{
//
}
+235
View File
@@ -0,0 +1,235 @@
<?php
namespace Illuminate\Contracts\Container;
use Closure;
use Psr\Container\ContainerInterface;
interface Container extends ContainerInterface
{
/**
* {@inheritdoc}
*
* @template TClass of object
*
* @param string|class-string<TClass> $id
* @return ($id is class-string<TClass> ? TClass : mixed)
*/
public function get(string $id);
/**
* Determine if the given abstract type has been bound.
*
* @param string $abstract
* @return bool
*/
public function bound($abstract);
/**
* Alias a type to a different name.
*
* @param string $abstract
* @param string $alias
* @return void
*
* @throws \LogicException
*/
public function alias($abstract, $alias);
/**
* Assign a set of tags to a given binding.
*
* @param array|string $abstracts
* @param array|mixed ...$tags
* @return void
*/
public function tag($abstracts, $tags);
/**
* Resolve all of the bindings for a given tag.
*
* @param string $tag
* @return iterable
*/
public function tagged($tag);
/**
* Register a binding with the container.
*
* @param \Closure|string $abstract
* @param \Closure|string|null $concrete
* @param bool $shared
* @return void
*/
public function bind($abstract, $concrete = null, $shared = false);
/**
* Bind a callback to resolve with Container::call.
*
* @param array|string $method
* @param \Closure $callback
* @return void
*/
public function bindMethod($method, $callback);
/**
* Register a binding if it hasn't already been registered.
*
* @param \Closure|string $abstract
* @param \Closure|string|null $concrete
* @param bool $shared
* @return void
*/
public function bindIf($abstract, $concrete = null, $shared = false);
/**
* Register a shared binding in the container.
*
* @param \Closure|string $abstract
* @param \Closure|string|null $concrete
* @return void
*/
public function singleton($abstract, $concrete = null);
/**
* Register a shared binding if it hasn't already been registered.
*
* @param \Closure|string $abstract
* @param \Closure|string|null $concrete
* @return void
*/
public function singletonIf($abstract, $concrete = null);
/**
* Register a scoped binding in the container.
*
* @param \Closure|string $abstract
* @param \Closure|string|null $concrete
* @return void
*/
public function scoped($abstract, $concrete = null);
/**
* Register a scoped binding if it hasn't already been registered.
*
* @param \Closure|string $abstract
* @param \Closure|string|null $concrete
* @return void
*/
public function scopedIf($abstract, $concrete = null);
/**
* "Extend" an abstract type in the container.
*
* @param \Closure|string $abstract
* @param \Closure $closure
* @return void
*
* @throws \InvalidArgumentException
*/
public function extend($abstract, Closure $closure);
/**
* Register an existing instance as shared in the container.
*
* @template TInstance of mixed
*
* @param \Closure|string $abstract
* @param TInstance $instance
* @return TInstance
*/
public function instance($abstract, $instance);
/**
* Add a contextual binding to the container.
*
* @param string $concrete
* @param \Closure|string $abstract
* @param \Closure|string $implementation
* @return void
*/
public function addContextualBinding($concrete, $abstract, $implementation);
/**
* Define a contextual binding.
*
* @param string|array $concrete
* @return \Illuminate\Contracts\Container\ContextualBindingBuilder
*/
public function when($concrete);
/**
* Get a closure to resolve the given type from the container.
*
* @template TClass of object
*
* @param string|class-string<TClass> $abstract
* @return ($abstract is class-string<TClass> ? \Closure(): TClass : \Closure(): mixed)
*/
public function factory($abstract);
/**
* Flush the container of all bindings and resolved instances.
*
* @return void
*/
public function flush();
/**
* Resolve the given type from the container.
*
* @template TClass of object
*
* @param string|class-string<TClass> $abstract
* @param array $parameters
* @return ($abstract is class-string<TClass> ? TClass : mixed)
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function make($abstract, array $parameters = []);
/**
* Call the given Closure / class@method and inject its dependencies.
*
* @param callable|string $callback
* @param array $parameters
* @param string|null $defaultMethod
* @return mixed
*/
public function call($callback, array $parameters = [], $defaultMethod = null);
/**
* Determine if the given abstract type has been resolved.
*
* @param string $abstract
* @return bool
*/
public function resolved($abstract);
/**
* Register a new before resolving callback.
*
* @param \Closure|string $abstract
* @param \Closure|null $callback
* @return void
*/
public function beforeResolving($abstract, ?Closure $callback = null);
/**
* Register a new resolving callback.
*
* @param \Closure|string $abstract
* @param \Closure|null $callback
* @return void
*/
public function resolving($abstract, ?Closure $callback = null);
/**
* Register a new after resolving callback.
*
* @param \Closure|string $abstract
* @param \Closure|null $callback
* @return void
*/
public function afterResolving($abstract, ?Closure $callback = null);
}
@@ -0,0 +1,8 @@
<?php
namespace Illuminate\Contracts\Container;
interface ContextualAttribute
{
//
}
@@ -0,0 +1,39 @@
<?php
namespace Illuminate\Contracts\Container;
interface ContextualBindingBuilder
{
/**
* Define the abstract target that depends on the context.
*
* @param string $abstract
* @return $this
*/
public function needs($abstract);
/**
* Define the implementation for the contextual binding.
*
* @param \Closure|string|array $implementation
* @return void
*/
public function give($implementation);
/**
* Define tagged services to be used as the implementation for the contextual binding.
*
* @param string $tag
* @return void
*/
public function giveTagged($tag);
/**
* Specify the configuration item to bind as a primitive.
*
* @param string $key
* @param mixed $default
* @return void
*/
public function giveConfig($key, $default = null);
}
+47
View File
@@ -0,0 +1,47 @@
<?php
namespace Illuminate\Contracts\Cookie;
interface Factory
{
/**
* Create a new cookie instance.
*
* @param string $name
* @param string $value
* @param int $minutes
* @param string|null $path
* @param string|null $domain
* @param bool|null $secure
* @param bool $httpOnly
* @param bool $raw
* @param string|null $sameSite
* @return \Symfony\Component\HttpFoundation\Cookie
*/
public function make($name, $value, $minutes = 0, $path = null, $domain = null, $secure = null, $httpOnly = true, $raw = false, $sameSite = null);
/**
* Create a cookie that lasts "forever" (five years).
*
* @param string $name
* @param string $value
* @param string|null $path
* @param string|null $domain
* @param bool|null $secure
* @param bool $httpOnly
* @param bool $raw
* @param string|null $sameSite
* @return \Symfony\Component\HttpFoundation\Cookie
*/
public function forever($name, $value, $path = null, $domain = null, $secure = null, $httpOnly = true, $raw = false, $sameSite = null);
/**
* Expire the given cookie.
*
* @param string $name
* @param string|null $path
* @param string|null $domain
* @return \Symfony\Component\HttpFoundation\Cookie
*/
public function forget($name, $path = null, $domain = null);
}
+30
View File
@@ -0,0 +1,30 @@
<?php
namespace Illuminate\Contracts\Cookie;
interface QueueingFactory extends Factory
{
/**
* Queue a cookie to send with the next response.
*
* @param mixed ...$parameters
* @return void
*/
public function queue(...$parameters);
/**
* Remove a cookie from the queue.
*
* @param string $name
* @param string|null $path
* @return void
*/
public function unqueue($name, $path = null);
/**
* Get the cookies which have been queued for the next request.
*
* @return array
*/
public function getQueuedCookies();
}
@@ -0,0 +1,14 @@
<?php
namespace Illuminate\Contracts\Database\Eloquent;
use Illuminate\Contracts\Database\Query\Builder as BaseContract;
/**
* This interface is intentionally empty and exists to improve IDE support.
*
* @mixin \Illuminate\Database\Eloquent\Builder
*/
interface Builder extends BaseContract
{
}
@@ -0,0 +1,14 @@
<?php
namespace Illuminate\Contracts\Database\Eloquent;
interface Castable
{
/**
* Get the name of the caster class to use when casting from / to this cast target.
*
* @param array $arguments
* @return class-string<CastsAttributes|CastsInboundAttributes>|CastsAttributes|CastsInboundAttributes
*/
public static function castUsing(array $arguments);
}
@@ -0,0 +1,34 @@
<?php
namespace Illuminate\Contracts\Database\Eloquent;
use Illuminate\Database\Eloquent\Model;
/**
* @template TGet
* @template TSet
*/
interface CastsAttributes
{
/**
* Transform the attribute from the underlying model values.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array<string, mixed> $attributes
* @return TGet|null
*/
public function get(Model $model, string $key, mixed $value, array $attributes);
/**
* Transform the attribute to its underlying model values.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param TSet|null $value
* @param array<string, mixed> $attributes
* @return mixed
*/
public function set(Model $model, string $key, mixed $value, array $attributes);
}
@@ -0,0 +1,19 @@
<?php
namespace Illuminate\Contracts\Database\Eloquent;
use Illuminate\Database\Eloquent\Model;
interface CastsInboundAttributes
{
/**
* Transform the attribute to its underlying model values.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array<string, mixed> $attributes
* @return mixed
*/
public function set(Model $model, string $key, mixed $value, array $attributes);
}

Some files were not shown because too many files have changed in this diff Show More